Sync — WaitGroup, Mutex, and RWMutex
Shared state in JavaScript vs Go
Section titled “Shared state in JavaScript vs Go”In JavaScript the event loop is single-threaded, so you almost never worry about concurrent writes to a shared variable (unless you are using SharedArrayBuffer with Worker Threads):
let count = 0;// safe in Node.js — only one callback runs at a timeasync function increment() { count++; }await Promise.all([increment(), increment()]);console.log(count); // always 2In Go goroutines run on real OS threads. Two goroutines can read and write the same memory location at the same instant, producing a data race — which is undefined behavior:
// DANGEROUS — data racecount := 0var wg sync.WaitGroupfor i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() count++ // read-modify-write is NOT atomic }()}wg.Wait()// count may not equal 100Mutex — mutual exclusion
Section titled “Mutex — mutual exclusion”sync.Mutex ensures only one goroutine is inside a critical section at a time. Lock before entering, unlock before leaving (use defer so an early return never forgets to unlock):
// TypeScript: no mutex needed for normal async code// With SharedArrayBuffer + Workers, use Atomics instead:const sab = new SharedArrayBuffer(4);const view = new Int32Array(sab);Atomics.add(view, 0, 1); // atomic increment// Go: sync.Mutex protects the critical sectionpackage main
import ( "fmt" "sync")
type SafeCounter struct { mu sync.Mutex count int}
func (c *SafeCounter) Increment() { c.mu.Lock() // acquire lock defer c.mu.Unlock() // release on return — always 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) // always 100}RWMutex — multiple readers, exclusive writer
Section titled “RWMutex — multiple readers, exclusive writer”When data is read frequently but written rarely, sync.RWMutex delivers better throughput than a plain Mutex. Multiple goroutines can hold a read lock simultaneously; a write lock is exclusive:
// TypeScript: no built-in RWMutex concept// In Worker Threads you use message-passing instead of shared locks// Go: RWMutex — concurrent reads, exclusive writespackage main
import ( "fmt" "sync")
type Cache struct { mu sync.RWMutex data map[string]string}
func (c *Cache) Get(key string) string { c.mu.RLock() // many goroutines can hold RLock at once defer c.mu.RUnlock() return c.data[key]}
func (c *Cache) Set(key, value string) { c.mu.Lock() // exclusive — all readers must finish first defer c.mu.Unlock() c.data[key] = value}
func main() { cache := &Cache{data: make(map[string]string)} cache.Set("lang", "Go") fmt.Println(cache.Get("lang")) // Go}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)…