select membuat goroutine menunggu beberapa operasi channel sekaligus, lalu menjalankan case yang pertama siap. Bentuknya mirip switch, tapi tiap case adalah kirim atau terima di channel. Bayangin pelayan Warung Bakso Mas Bimo berdiri di depan dapur. Bakso urat dan es teh sedang disiapkan di dua tempat berbeda, dan pelayan mengantar mana pun yang siap duluan. Sambil menunggu, ia melirik jam: kalau kelamaan, ia kembali ke pembeli dan bilang maaf. Dua tempat masakan itu channel, jamnya time.After.
🧪 Tebakan cepat: bakso urat siap dalam 3 detik, es teh dalam 1 detik, dan
time.Aftermemberi batas 2 detik. Apa yang dicetak program di bawah? Tebak dulu, jawabannya ada di bagian "select: yang siap duluan".
select: yang siap duluan
package main
import (
"fmt"
"time"
)
func masak(menu string, lama time.Duration) <-chan string {
ch := make(chan string, 1)
go func() {
time.Sleep(lama)
ch <- menu + " siap"
}()
return ch
}
func main() {
bakso := masak("bakso urat", 3*time.Second)
esTeh := masak("es teh", 1*time.Second)
select {
case msg := <-bakso:
fmt.Println(msg)
case msg := <-esTeh:
fmt.Println(msg)
case <-time.After(2 * time.Second):
fmt.Println("timeout")
}
}
$ go run bakso.go
es teh siap
masakmemulai goroutine yang tidur selamalama, lalu mengirim"<menu> siap". Ia mengembalikan channel<-chan string, channel yang cuma boleh diterima.selectmenunggu sampai salah satu case siap. Es teh siap di detik 1, lebih dulu dari batas 2 detik dan bakso 3 detik, jadi tercetakes teh siap. Itu jawaban tebakannya.- Cuma satu case yang dijalankan. Bakso yang siap belakangan diabaikan oleh
selectini. - Kalau beberapa case siap bersamaan,
selectmemilih salah satunya secara acak. Urutan case di kode gak memberi prioritas.
Timeout dengan time.After
time.After(d) mengembalikan channel yang menerima satu nilai setelah durasi d. Dipasang sebagai case, ia jadi batas waktu. Ubah es teh jadi 4 detik, dan gak ada masakan yang siap sebelum detik 2:
$ go run bakso.go
timeout
Pola dasarnya:
select {
case msg := <-ch:
fmt.Println(msg)
case <-time.After(1 * time.Second):
fmt.Println("timeout")
}
Kalau ch gak mengirim apa-apa dalam 1 detik, case kedua yang jalan dan tercetak timeout. Perhatikan make(chan string, 1) di masak: buffer 1 membuat goroutine-nya tetap bisa mengirim dan selesai walau gak ada lagi yang menerima. Tanpa buffer, goroutine bakso yang kalah akan menunggu selamanya (goroutine leak).
default: jangan menunggu
Tambahkan default, dan select gak pernah menunggu. Kalau saat itu gak ada case yang siap, default langsung jalan:
select {
case msg := <-bakso:
fmt.Println(msg)
case msg := <-esTeh:
fmt.Println(msg)
case <-time.After(2 * time.Second):
fmt.Println("timeout")
default:
fmt.Println("belum ada yang siap")
}
Program ini mencetak belum ada yang siap di detik 0. default juga berguna untuk mengirim tanpa menunggu: select { case antrean <- m: ... default: fmt.Println("antrean penuh, tolak:", m) } menolak pesanan kalau antreannya penuh.
💡 Menghentikan goroutine: done channel dan context
Goroutine yang terus bekerja bisa diberi tanda berhenti lewat channel. close(done) membuat semua <-done langsung siap:
func dapur(done <-chan struct{}) {
for {
select {
case <-done:
fmt.Println("dapur: kompor dimatikan")
return
default:
fmt.Println("dapur: rebus kuah")
time.Sleep(400 * time.Millisecond)
}
}
}
Kalau main menunggu 1 detik lalu close(done), output-nya tiga kali dapur: rebus kuah lalu dapur: kompor dimatikan. Cara yang dipakai package standar adalah context: ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second), lalu case <-ctx.Done():. Saat batasnya lewat, ctx.Err() berisi context deadline exceeded. Selalu panggil defer cancel().
Pola lain yang dibangun dari channel dan select: worker pool (beberapa goroutine tetap mengambil pekerjaan dari satu channel), fan-in (hasil banyak goroutine dikumpulkan ke satu channel), dan pipeline (keluaran satu tahap jadi masukan tahap berikutnya).
⚠️ Jebakan yang sering ketemu
- Gak ada case yang bisa siap.
selecttanpadefaultmenunggu selamanya; kalau gak ada goroutine lain, program mati denganfatal error: all goroutines are asleep - deadlock!dangoroutine 1 [select].select {}kosong juga menunggu selamanya. - Mengira case pertama didahulukan. Kalau dua case siap bersamaan, pilihannya acak.
- Goroutine yang ditinggal timeout. Channel hasilnya tanpa buffer, dan gak ada yang menerima lagi: goroutine-nya macet selamanya. Pakai buffer 1.
defaultdi dalamfortanpa jeda. Loop-nya berputar terus tanpa menunggu dan menghabiskan CPU. Beri jeda, atau buangdefaultsupayaselectmenunggu.
TL;DR: select menunggu beberapa operasi channel sekaligus dan menjalankan satu case yang pertama siap; kalau beberapa siap bersamaan, dipilih acak. case <-time.After(1 * time.Second): fmt.Println("timeout") memberi batas waktu. default membuat select gak menunggu sama sekali. Hentikan goroutine dengan close(done) atau context (case <-ctx.Done()). select tanpa case yang bisa siap menunggu selamanya, dan beri channel hasil buffer 1 supaya goroutine yang kalah gak macet.