Concurrency: แบบฉบับ Go
โมเดล Concurrency ของ Go
หัวข้อที่มีชื่อว่า “โมเดล Concurrency ของ Go”ภาษาส่วนใหญ่เพิ่ม concurrency เข้าไปบน shared-memory threads ภายหลัง แต่ Go ถูกออกแบบตั้งแต่ต้นรอบแนวคิดที่แตกต่างออกไปเรียกว่า Communicating Sequential Processes (CSP) ซึ่ง Tony Hoare เป็นผู้นิยามในปี 1978 คำพูดของ Go ที่สรุปได้ชัดที่สุดคือ:
อย่าสื่อสารโดยการแชร์หน่วยความจำ ให้แชร์หน่วยความจำโดยการสื่อสารแทน
ในทางปฏิบัติหมายความว่า:
- Goroutines คือฟังก์ชันที่ทำงานอิสระ ใช้ทรัพยากรน้อยมาก — stack เริ่มต้นเพียงไม่กี่ KB ที่ขยายตัวได้ตามต้องการ — จึงสามารถมีได้หลายแสนตัวที่รันพร้อมกัน
- Channels คือท่อส่งข้อมูลแบบ typed goroutines ส่งค่าเข้า channel และรับค่าจาก channel อีกตัว ทั้งการส่งข้อมูลและการ synchronize เกิดขึ้นในคำสั่งเดียว
- Scheduler จัดสรร goroutines ลงบน OS threads (GOMAXPROCS threads ตามค่าเริ่มต้น หนึ่งตัวต่อ CPU core) คุณไม่ต้องจัดการ threads โดยตรง
โมดูลนี้ครอบคลุมอะไรบ้าง
หัวข้อที่มีชื่อว่า “โมดูลนี้ครอบคลุมอะไรบ้าง”| บทเรียน | หัวข้อ |
|---|---|
| หน้านี้ | ภาพรวมโมเดลและตัวอย่างแรกที่รันได้ |
| Goroutines | go f(), scheduler, sync.WaitGroup |
| Channels | สร้าง, ส่ง, รับ, unbuffered vs buffered, close, range |
| Select | รวม channels หลายตัว, default, timeouts |
| Sync Primitives | sync.Mutex, sync.Once, sync/atomic |
| Context | context.Context, การยกเลิก, การส่งต่อ |
| Patterns | Worker pool, fan-out/fan-in, pipeline |
โปรแกรม concurrent ตัวแรกของคุณ
หัวข้อที่มีชื่อว่า “โปรแกรม concurrent ตัวแรกของคุณ”snippet ด้านล่างสร้าง goroutines 3 ตัวแบบขนาน แต่ละตัวเขียนผลลัพธ์ลงใน slice ที่จัดสรรไว้ล่วงหน้า indexed ตาม id ของตัวเอง ทำให้ผลลัพธ์เรียงลำดับเสมอ sync.WaitGroup บล็อก main จนกว่าทั้งสามตัวจะเสร็จสิ้น
package main
import ( "fmt" "sync")
func main() { var wg sync.WaitGroup results := make([]string, 3)
for i := 0; i < 3; i++ { wg.Add(1) go func(id int) { defer wg.Done() results[id] = fmt.Sprintf("goroutine %d says hello", id) }(i) }
wg.Wait() for _, r := range results { fmt.Println(r) } fmt.Println("main exits")}Loading Go runtime (first run only, ~8 MB)…
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| สิ่งที่ได้ | ประโยชน์ | ต้นทุน |
|---|---|---|
| goroutine vs OS thread | 2-8 KB stack vs 1-8 MB | scheduler overhead |
| CSP (channel) vs shared memory | ชัดเจนว่า data flow ไปไหน | channel overhead สูงกว่า mutex |
| M:N scheduling | ใช้ OS thread น้อย | scheduler complexity |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- goroutine = OS thread — goroutine เบากว่า thread มาก สร้างได้หลักแสนตัว
- concurrent = parallel — concurrent คือจัดการหลาย task พร้อมกัน, parallel คือรันพร้อมกันจริงบน multi-core
- channel เสมอดีกว่า mutex — mutex เหมาะกับ simple shared state, channel เหมาะกับ data flow
💡 ตัวอย่างจากของจริง
Kubernetes ใช้ goroutine pool จัดการ controller loop ของทุก resource type พร้อมกัน
Prometheus เปิด goroutine ต่อ scrape target ทำให้ collect metrics แบบ parallel