Sync — WaitGroup, Mutex และ RWMutex
Shared State ใน JavaScript vs Go
หัวข้อที่มีชื่อว่า “Shared State ใน JavaScript vs Go”ใน JavaScript event loop เป็น single-threaded คุณจึงแทบไม่ต้องกังวลเรื่องการเขียน shared variable พร้อมกัน (ยกเว้นตอนใช้ SharedArrayBuffer กับ Worker Threads):
let count = 0;// ปลอดภัยใน Node.js เพราะ single-threadedasync function increment() { count++; }await Promise.all([increment(), increment()]);console.log(count); // 2 — ถูกต้องเสมอใน Go goroutine รันบน OS thread จริง goroutine สองตัวจึงอ่านและเขียน memory ตำแหน่งเดียวกันในจังหวะเดียวกันได้ ผลที่ได้คือ data race — undefined behavior เต็ม ๆ:
// อันตราย! Data race!count := 0var wg sync.WaitGroupfor i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done(); count++ }() // race!}wg.Wait()// count อาจไม่เท่ากับ 100Mutex — Mutual Exclusion Lock
หัวข้อที่มีชื่อว่า “Mutex — Mutual Exclusion Lock”sync.Mutex รับประกันว่ามี goroutine เดียวเท่านั้นที่อยู่ใน critical section ได้ในแต่ละช่วงเวลา lock ก่อนเข้า unlock ก่อนออก (ใช้ defer เพื่อกันไม่ให้ early return ลืม unlock):
// TypeScript: ไม่จำเป็นต้องใช้ mutex ใน event loop ปกติ// แต่ถ้าใช้ SharedArrayBuffer กับ Workers:const sab = new SharedArrayBuffer(4);const view = new Int32Array(sab);Atomics.add(view, 0, 1); // atomic increment// Go: sync.Mutex ปกป้อง shared statepackage main
import ( "fmt" "sync")
type SafeCounter struct { mu sync.Mutex count int}
func (c *SafeCounter) Increment() { c.mu.Lock() // ล็อก defer c.mu.Unlock() // ปลดล็อกเมื่อ function return c.count++}
func main() { var wg sync.WaitGroup counter := &SafeCounter{}
for i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() counter.Increment() }() } wg.Wait() fmt.Println("Final count:", counter.count) // 100 เสมอ}RWMutex — อ่านได้พร้อมกัน เขียนได้ครั้งเดียว
หัวข้อที่มีชื่อว่า “RWMutex — อ่านได้พร้อมกัน เขียนได้ครั้งเดียว”เมื่อ data ถูกอ่านบ่อยแต่เขียนน้อย sync.RWMutex ให้ throughput ดีกว่า Mutex ธรรมดา หลาย goroutine ถือ read lock พร้อมกันได้ ส่วน write lock เป็น exclusive:
// TypeScript: ไม่มี built-in RWMutex concept// ใน Workers ต้องใช้ message passing แทน// Go: RWMutex — readers พร้อมกัน, writer exclusivepackage main
import ( "fmt" "sync")
type Cache struct { mu sync.RWMutex data map[string]string}
func (c *Cache) Get(key string) string { c.mu.RLock() // หลาย goroutines อ่านพร้อมกันได้ defer c.mu.RUnlock() return c.data[key]}
func (c *Cache) Set(key, value string) { c.mu.Lock() // เขียน: exclusive lock defer c.mu.Unlock() c.data[key] = value}package main
import ( "fmt" "sync")
type SafeCounter struct { mu sync.Mutex count int}
func (c *SafeCounter) Increment() { c.mu.Lock() defer c.mu.Unlock() c.count++}
func (c *SafeCounter) Value() int { c.mu.Lock() defer c.mu.Unlock() return c.count}
func main() { var wg sync.WaitGroup counter := &SafeCounter{}
for i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() counter.Increment() }() } wg.Wait() fmt.Println("Final count:", counter.Value())}Loading Go runtime (first run only, ~8 MB)…