Synchronization in Go

Synchronization is crucial in concurrent programming to ensure safe access to shared resources and coordinate the execution of multiple goroutines. Go provides several synchronization primitives, including Mutex, RWMutex, WaitGroup, and Once, which help manage concurrency effectively.

1. Mutexes and RWMutexes

Mutex

A Mutex (mutual exclusion lock) ensures that only one goroutine can access a critical section of code at a time, preventing race conditions.

go
package main import ( "fmt" "sync" ) var ( counter int mu sync.Mutex ) func increment() { mu.Lock() defer mu.Unlock() counter++ } func main() { var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() increment() }() } wg.Wait() fmt.Println("Final Counter:", counter) // Output: Final Counter: 1000 }

RWMutex

An RWMutex (read-write mutex) allows multiple readers but only one writer, optimizing for read-heavy workloads.

go
package main import ( "fmt" "sync" ) var ( data = make(map[int]int) rwMu sync.RWMutex wg sync.WaitGroup ) func write(key, value int) { rwMu.Lock() defer rwMu.Unlock() data[key] = value } func read(key int) int { rwMu.RLock() defer rwMu.RUnlock() return data[key] } func main() { // Writing data for i := 0; i < 5; i++ { wg.Add(1) go func(i int) { defer wg.Done() write(i, i*i) }(i) } wg.Wait() // Reading data for i := 0; i < 5; i++ { wg.Add(1) go func(i int) { defer wg.Done() fmt.Printf("data[%d] = %d\n", i, read(i)) }(i) } wg.Wait() }

2. WaitGroups and Once

WaitGroup

A WaitGroup waits for a collection of goroutines to finish executing.

go
package main import ( "fmt" "sync" ) func worker(id int, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Worker %d starting\n", id) // Simulate work fmt.Printf("Worker %d done\n", id) } func main() { var wg sync.WaitGroup for i := 1; i <= 5; i++ { wg.Add(1) go worker(i, &wg) } wg.Wait() fmt.Println("All workers done") }

Once

A Once ensures a piece of code runs only once, regardless of how many goroutines invoke it.

go
package main import ( "fmt" "sync" ) var once sync.Once func initialize() { fmt.Println("Initialization done") } func worker(wg *sync.WaitGroup) { defer wg.Done() once.Do(initialize) fmt.Println("Worker running") } func main() { var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go worker(&wg) } wg.Wait() fmt.Println("All workers done") }

Summary

Synchronization in Go is essential for managing concurrency and ensuring safe access to shared resources. Mutex and RWMutex provide mechanisms for exclusive and shared access, respectively, while WaitGroup and Once help coordinate the execution of multiple goroutines. By mastering these synchronization primitives, you can write robust and efficient concurrent programs in Go.

Becoming a Senior Go Developer: Mastering Go and Its Ecosystem