Channels
Channel คืออะไร?
หัวข้อที่มีชื่อว่า “Channel คืออะไร?”Channel คือท่อส่งข้อมูลแบบ typed ที่ goroutines ใช้สื่อสาร การส่งค่าเข้า channel และรับจาก channel เป็นสองคำสั่งที่ถ่ายโอนทั้งข้อมูลและการ synchronize ในขั้นตอนเดียว
ch := make(chan int) // unbuffered channel ของ intch <- 42 // ส่ง (บล็อกจนกว่า receiver พร้อม)v := <-ch // รับ (บล็อกจนกว่า sender พร้อม)Type system บังคับสิ่งที่ไหลผ่าน channel chan string ไม่ยอมรับ int
Unbuffered vs Buffered
หัวข้อที่มีชื่อว่า “Unbuffered vs Buffered”| Unbuffered | Buffered | |
|---|---|---|
| สร้างด้วย | make(chan T) | make(chan T, capacity) |
| Send บล็อก? | ใช่ — จนกว่า receiver พร้อม | ไม่ — จนกว่า buffer เต็ม |
| Receive บล็อก? | ใช่ — จนกว่า sender พร้อม | ไม่ — จนกว่า buffer ว่าง |
| Synchronize? | ใช่ — sender และ receiver พบกันในเวลาเดียวกัน | ไม่ — buffer แยกเวลาออกจากกัน |
Unbuffered channels ให้ synchronization guarantee ที่แข็งแกร่งที่สุด: sender รู้ว่า receiver ได้รับค่าแล้วก่อนที่จะดำเนินต่อ
ทิศทางของ Channel
หัวข้อที่มีชื่อว่า “ทิศทางของ Channel”คุณสามารถจำกัด channel parameter ให้เป็น send-only หรือ receive-only เพื่อแสดง intent ใน type:
func producer(out chan<- int) { out <- 1 } // send-onlyfunc consumer(in <-chan int) { v := <-in; _ = v } // receive-onlyการปิด Channel และ Range
หัวข้อที่มีชื่อว่า “การปิด Channel และ Range”Sender เรียก close(ch) เพื่อบอกว่าจะไม่มีค่าอีกแล้ว Receivers ใช้ for range loop เพื่อดึงค่าทั้งหมดจนกว่าจะ close:
close(ch)for v := range ch { // exit เมื่อ ch ถูกปิดและว่าง fmt.Println(v)}Comma-ok Receive Idiom
หัวข้อที่มีชื่อว่า “Comma-ok Receive Idiom”Receive expression สามารถ return boolean ตัวที่สองที่บอกว่า channel ยังเปิดอยู่หรือไม่:
v, ok := <-ch// ok == true → v เป็นค่าจริง// ok == false → channel ถูกปิดและดึงออกหมดแล้ว v เป็น zero valuepackage main
import "fmt"
func producer(ch chan<- int, n int) { for i := 1; i <= n; i++ { ch <- i } close(ch)}
func main() { // Unbuffered channel: producer รันใน goroutine ch := make(chan int) go producer(ch, 5) for v := range ch { fmt.Println(v) }
// Buffered channel: ส่งทั้งหมดพอดี buffer buf := make(chan string, 3) buf <- "a" buf <- "b" buf <- "c" close(buf) for s := range buf { fmt.Println(s) }
// Comma-ok: แยกค่าจริงจาก zero value ของ channel ที่ปิด done := make(chan bool, 1) done <- true close(done) v1, ok1 := <-done fmt.Println(v1, ok1) // true true v2, ok2 := <-done fmt.Println(v2, ok2) // false false}Loading Go runtime (first run only, ~8 MB)…
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”Unbuffered channel — rendez-vous point:
G1 ──send──→ [channel] ←──recv── G2 (block) (block) G1 และ G2 ต้องพบกัน
Buffered channel (cap=3):
G1 ──send──→ [_][_][_] ←──recv── G2 (block เมื่อ full) (block เมื่อ empty) G1/G2 ทำงานอิสระมากกว่าข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| สิ่งที่ได้ | ประโยชน์ | ต้นทุน |
|---|---|---|
| unbuffered channel | synchronization guarantee แข็งแกร่ง | G ส่ง block จนกว่า G รับพร้อม |
| buffered channel | decouple sender/receiver timing | ต้อง size buffer ให้เหมาะ, อาจ hide backpressure |
channel direction (chan<-, <-chan) | compile-time safety | verbose ใน function signature |
| close + range | notify completion ง่าย | ส่งหลัง close → panic |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- channel เสมอเร็วกว่า mutex — channel มี overhead มากกว่า mutex สำหรับ simple counter
- buffered channel ช่วยป้องกัน deadlock — ไม่เสมอ — deadlock ยังเกิดได้ถ้า buffer เต็มและไม่มี receiver
- close(ch) ลบข้อมูลใน buffer — ไม่ — receiver ยังรับข้อมูลที่เหลือใน buffer ได้หลัง close
- สามารถ close channel จาก receiver ได้ — sender เท่านั้นที่ควร close channel
💡 ตัวอย่างจากของจริง
net/http ใช้ channel pattern ภายใน สำหรับ read/write pipeline ระหว่าง connection handler กับ request body
Kubernetes ใช้ event channel ส่ง object changes ไป controller loop
Docker ใช้ channel เชื่อม container process กับ log collector สำหรับ container log streaming