ข้ามไปยังเนื้อหา

Select

select เป็นวิธีของ Go ในการรอหลาย channel operations พร้อมกัน โดยบล็อกจนกว่า case หนึ่งพร้อม แล้วรัน case นั้น ถ้าหลาย case พร้อมพร้อมกัน จะเลือกหนึ่งแบบ สุ่ม — นี่คือการออกแบบภาษาโดยตั้งใจเพื่อหลีกเลี่ยง starvation

select {
case v := <-ch1:
fmt.Println("received from ch1:", v)
case ch2 <- 99:
fmt.Println("sent to ch2")
}

select case สามารถเป็น send หรือ receive — คำสั่งไหนก็ได้ที่ดำเนินต่อได้โดยไม่บล็อก

การเพิ่ม default case ทำให้ select เป็น non-blocking ถ้าไม่มี channel พร้อม branch default จะรันทันที:

select {
case v := <-ch:
fmt.Println("got", v)
default:
fmt.Println("nothing ready yet")
}

Pattern ทั่วไปคือการรวม select กับ time.After ซึ่ง return channel ที่รับค่าหลังจาก duration ที่กำหนด:

select {
case result := <-work:
fmt.Println("done:", result)
case <-time.After(2 * time.Second):
fmt.Println("timed out")
}

สำหรับ production ที่ตั้ง timeouts หลายจุด ให้ใช้ context.WithTimeout แทนเพื่อ propagate cancellation และหลีกเลี่ยง timer leak

package main
import "fmt"
func main() {
// โหลด buffered channels สองตัวล่วงหน้า
ch1 := make(chan string, 1)
ch2 := make(chan string, 1)
ch1 <- "one"
ch2 <- "two"
// ดึง ch1 ด้วย select ของตัวเอง — รับ "one" แน่นอน
select {
case msg := <-ch1:
fmt.Println("ch1:", msg)
}
// ดึง ch2 ด้วย select ของตัวเอง — รับ "two" แน่นอน
select {
case msg := <-ch2:
fmt.Println("ch2:", msg)
}
// default ทำให้ select เป็น non-blocking
ch3 := make(chan int)
select {
case v := <-ch3:
fmt.Println("received", v)
default:
fmt.Println("no value ready, default taken")
}
// Fan-in: select จาก channel ที่มีค่าพร้อม
// (ใช้ select แยกกันเพื่อให้ output deterministic ใน playground)
r1 := make(chan int, 1)
r2 := make(chan int, 1)
r1 <- 10
r2 <- 20
select {
case v := <-r1:
fmt.Println("r1:", v)
}
select {
case v := <-r2:
fmt.Println("r2:", v)
}
fmt.Println("done")
}
สิ่งที่ได้ประโยชน์ต้นทุน
select over multiple channelshandle หลาย event source พร้อมกันrandom selection เมื่อหลาย case ready
default casenon-blocking checkloop tight ทำให้ CPU spin
timeout with time.Afterกำหนด deadline ง่ายleak timer goroutine ถ้าไม่ drain channel
select กับ context.Done()cancellation ง่ายต้องใส่ทุก select ที่อาจรันนาน
  • select เหมือน switch — select เลือกแบบ random ถ้าหลาย case ready — ไม่ใช่ first-match
  • default ใน select ทำให้เร็วขึ้น — default loop แบบ tight = CPU spinning = ใช้ CPU สูง
  • time.After ไม่ leak — time.After สร้าง timer goroutine ใหม่ทุกครั้ง — ใน tight loop ควรใช้ time.NewTimer แทน

💡 ตัวอย่างจากของจริง

Kubernetes controller loop ใช้ select รอ channel หลายตัว: workqueue, stopCh, ticker สำหรับ reconciliation

Go HTTP server ใช้ select { case <-ctx.Done(): } ใน handler เพื่อ cancel งานเมื่อ client disconnect

Go ทำอะไรเมื่อหลาย case ใน select พร้อมพร้อมกัน?
การเพิ่ม `default` case ใน select มีผลอย่างไร?
`time.After(d)` return อะไร?