Select
คำสั่ง select
หัวข้อที่มีชื่อว่า “คำสั่ง 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 Clause
หัวข้อที่มีชื่อว่า “Default Clause”การเพิ่ม default case ทำให้ select เป็น non-blocking ถ้าไม่มี channel พร้อม branch default จะรันทันที:
select {case v := <-ch: fmt.Println("got", v)default: fmt.Println("nothing ready yet")}Timeouts
หัวข้อที่มีชื่อว่า “Timeouts”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")}Loading Go runtime (first run only, ~8 MB)…
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| สิ่งที่ได้ | ประโยชน์ | ต้นทุน |
|---|---|---|
| select over multiple channels | handle หลาย event source พร้อมกัน | random selection เมื่อหลาย case ready |
| default case | non-blocking check | loop 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