Overview of Synchronization Mechanisms in Go
Go provides several built-in synchronization tools to manage concurrent access to shared resources and prevent race conditions.
Mutex (Mutual Exclusion)
package main
import (
"fmt"
"sync"
)
type SafeCounter struct {
mu sync.Mutex
value map[string]int
}
func (c *SafeCounter) Increment(key string) {
c.mu.Lock()
defer c.mu.Unlock()
c.value[key]++
}
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value[key]
}
func main() {
c := SafeCounter{
value: make(map[string]int),
}
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
c.Increment("key")
}()
}
wg.Wait()
fmt.Println("Final value:", c.Value("key"))
}
RWMutex (Read-Write Mutex)
package main
import (
"fmt"
"sync"
"time"
)
type Counter struct {
mu sync.RWMutex
value int
}
func (c *Counter) Read() int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.value
}
func (c *Counter) Write(n int) {
c.mu.Lock()
defer c.mu.Unlock()
c.value = n
}
func main() {
counter := &Counter{}
go func() {
for i := 0; i < 10; i++ {
counter.Write(i)
time.Sleep(time.Millisecond)
}
}()
for i := 0; i < 10; i++ {
fmt.Println("Read value:", counter.Read())
time.Sleep(time.Millisecond * 5)
}
}
Tool |
Purpose |
Use Case |
Overhead |
Mutex |
Exclusive access |
Protecting shared resources |
Low |
RWMutex |
Multiple readers, single writer |
Read-heavy scenarios |
Moderate |
WaitGroup |
Waiting for goroutines |
Concurrent task coordination |
Low |
Atomic Operations |
Simple numeric operations |
Performance-critical code |
Minimal |
WaitGroup
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
// Simulate work
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 complete")
}
Atomic Operations
package main
import (
"fmt"
"sync/atomic"
"time"
)
func main() {
var counter int64 = 0
for i := 0; i < 1000; i++ {
go func() {
atomic.AddInt64(&counter, 1)
}()
}
time.Sleep(time.Second)
fmt.Println("Final counter:", atomic.LoadInt64(&counter))
}
Synchronization Flow
graph TD
A[Concurrent Request] --> B{Synchronization Tool}
B -->|Mutex| C[Exclusive Access]
B -->|RWMutex| D[Read/Write Control]
B -->|WaitGroup| E[Goroutine Coordination]
B -->|Atomic| F[Lock-free Operation]
Best Practices
- Choose the right synchronization tool
- Minimize lock duration
- Avoid nested locks
- Use channels for complex synchronization
- Mutexes introduce slight performance overhead
- Atomic operations are most efficient
- Channels provide a more Go-idiomatic approach
At LabEx, we emphasize understanding these synchronization tools to build robust concurrent applications.