Sync Mechanisms
Introduction to Synchronization
Synchronization is crucial for managing concurrent access to shared resources in Go. The sync
package provides primitives to safely coordinate goroutines and prevent race conditions.
Mutex (Mutual Exclusion)
Mutexes prevent multiple goroutines from accessing shared resources simultaneously:
package main
import (
"fmt"
"sync"
)
type SafeCounter struct {
mu sync.Mutex
value int
}
func (c *SafeCounter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func main() {
counter := &SafeCounter{}
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println("Final value:", counter.value)
}
Synchronization Mechanisms Comparison
Mechanism |
Use Case |
Characteristics |
Mutex |
Exclusive access |
Blocks other goroutines |
RWMutex |
Read-heavy scenarios |
Allows multiple readers |
WaitGroup |
Waiting for goroutines |
Synchronizes goroutine completion |
Atomic Operations |
Simple counters |
Lock-free synchronization |
RWMutex (Read-Write Mutex)
Allows multiple readers or a single writer:
package main
import (
"fmt"
"sync"
"time"
)
type SafeCache struct {
mu sync.RWMutex
data map[string]string
}
func (c *SafeCache) Read(key string) string {
c.mu.RLock()
defer c.mu.RUnlock()
return c.data[key]
}
func (c *SafeCache) Write(key, value string) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = value
}
WaitGroup Synchronization
Coordinates multiple goroutines:
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
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 completed")
}
Synchronization Flow
sequenceDiagram
participant G1 as Goroutine 1
participant M as Mutex
participant R as Shared Resource
participant G2 as Goroutine 2
G1->>M: Request Lock
M-->>G1: Lock Granted
G1->>R: Modify Resource
G1->>M: Release Lock
G2->>M: Request Lock
Atomic Operations
For simple, lock-free synchronization:
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var counter int64 = 0
atomic.AddInt64(&counter, 1)
fmt.Println("Atomic counter:", counter)
}
Best Practices
- Minimize lock duration
- Avoid nested locks
- Use appropriate synchronization mechanism
- Prefer channels for complex synchronization
LabEx recommends practicing these synchronization techniques to build robust concurrent applications in Go.