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

Sync — WaitGroup, Mutex และ RWMutex

ใน JavaScript event loop เป็น single-threaded คุณจึงแทบไม่ต้องกังวลเรื่องการเขียน shared variable พร้อมกัน (ยกเว้นตอนใช้ SharedArrayBuffer กับ Worker Threads):

let count = 0;
// ปลอดภัยใน Node.js เพราะ single-threaded
async 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 := 0
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() { defer wg.Done(); count++ }() // race!
}
wg.Wait()
// count อาจไม่เท่ากับ 100

sync.Mutex รับประกันว่ามี goroutine เดียวเท่านั้นที่อยู่ใน critical section ได้ในแต่ละช่วงเวลา lock ก่อนเข้า unlock ก่อนออก (ใช้ defer เพื่อกันไม่ให้ early return ลืม unlock):

TypeScript
// TypeScript: ไม่จำเป็นต้องใช้ mutex ใน event loop ปกติ
// แต่ถ้าใช้ SharedArrayBuffer กับ Workers:
const sab = new SharedArrayBuffer(4);
const view = new Int32Array(sab);
Atomics.add(view, 0, 1); // atomic increment
Go
// Go: sync.Mutex ปกป้อง shared state
package 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 เสมอ
}

เมื่อ data ถูกอ่านบ่อยแต่เขียนน้อย sync.RWMutex ให้ throughput ดีกว่า Mutex ธรรมดา หลาย goroutine ถือ read lock พร้อมกันได้ ส่วน write lock เป็น exclusive:

TypeScript
// TypeScript: ไม่มี built-in RWMutex concept
// ใน Workers ต้องใช้ message passing แทน
Go
// Go: RWMutex — readers พร้อมกัน, writer exclusive
package 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())
}
ทำไมถึงเกิด data race ใน Go แต่ไม่เกิดใน JavaScript ปกติ?
RWMutex แตกต่างจาก Mutex ธรรมดาอย่างไร?
flag ใดของ go run ที่ใช้ตรวจหา data races?