Select & Patterns — Go

Select Statement select memungkinkan goroutine menunggu di beberapa channel sekaligus. Mirip switch, tapi untuk channel operations. Sintaks Dasar select {…

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.After memberi 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

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

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.

Yang akan kamu pelajari