Deep Dive into Go's sync Package: Mutex, RWMutex, WaitGroup, Once, Cond, Pool, and sync.Map
This article provides a comprehensive guide to Go's sync package, explaining the purpose, usage, code examples, and best‑practice recommendations for each synchronization primitive such as Mutex, RWMutex, WaitGroup, Once, Cond, Pool, and sync.Map.
Go is known for its excellent concurrency performance, and the sync package is the core toolkit for efficient concurrent programming. This article deeply analyzes the various synchronization primitives provided by the sync package to help you master Go concurrency.
1. Mutex
Mutex is the most basic synchronization primitive, used to protect exclusive access to shared resources.
var count int
var mu sync.Mutex
func increment() {
mu.Lock()
count++
mu.Unlock()
}Key points:
Use Lock() and Unlock() methods to acquire and release the lock.
Prefer defer mu.Unlock() to guarantee the lock is released.
Suitable for scenarios with many write operations.
2. RWMutex
RWMutex builds on Mutex by distinguishing read and write locks, allowing multiple readers while writes remain exclusive.
var cache map[string]string
var rwMu sync.RWMutex
func get(key string) string {
rwMu.RLock()
defer rwMu.RUnlock()
return cache[key]
}
func set(key, value string) {
rwMu.Lock()
defer rwMu.Unlock()
cache[key] = value
}Advantages:
Allows multiple goroutines to acquire the read lock simultaneously.
Write lock is exclusive.
Ideal for read‑heavy, write‑light scenarios.
3. WaitGroup
WaitGroup is used to wait for a collection of goroutines to finish their tasks.
var wg sync.WaitGroup
func worker(id int) {
defer wg.Done()
fmt.Printf("Worker %d working\n", id)
}
func main() {
for i := 0; i < 5; i++ {
wg.Add(1)
go worker(i)
}
wg.Wait()
fmt.Println("All workers done")
}Usage points:
Call Add() before starting each goroutine.
Done() is equivalent to Add(-1) .
Wait() blocks until the counter reaches zero.
4. Once
Once guarantees that a particular operation is executed only once, making it ideal for thread‑safe initialization and singleton patterns.
var (
config map[string]string
once sync.Once
)
func loadConfig() {
once.Do(func() {
// initialize configuration
config = make(map[string]string)
config["key"] = "value"
})
}Features:
Thread‑safe initialization.
Suitable for singleton implementations.
Even if called multiple times, the function runs only once.
5. Cond
Cond provides condition variables for goroutine coordination, allowing one goroutine to wait for a condition and another to signal it.
var (
ready bool
cond = sync.NewCond(&sync.Mutex{})
)
func worker() {
time.Sleep(time.Second)
cond.L.Lock()
ready = true
cond.Signal() // wake up one waiting goroutine
cond.L.Unlock()
}
func main() {
go worker()
cond.L.Lock()
for !ready {
cond.Wait()
}
cond.L.Unlock()
fmt.Println("Ready!")
}Applicable scenarios:
Producer‑consumer patterns.
Event notification mechanisms.
6. Pool
Pool caches and reuses temporary objects, reducing allocations and GC pressure.
var bufPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}
func getBuffer() *bytes.Buffer { return bufPool.Get().(*bytes.Buffer) }
func putBuffer(buf *bytes.Buffer) {
buf.Reset()
bufPool.Put(buf)
}Advantages:
Reduces memory allocations.
Lowers garbage‑collection overhead.
Ideal for objects that are frequently created and destroyed.
7. sync.Map
sync.Map is a thread‑safe map implementation that avoids the need for explicit locking.
var m sync.Map
func main() {
m.Store("key", "value")
value, ok := m.Load("key")
m.Delete("key")
m.Range(func(k, v interface{}) bool {
fmt.Println(k, v)
return true
})
}Features:
No extra locking required.
Best for read‑heavy, write‑light workloads.
Generally faster than a map protected by a Mutex .
Performance Comparison and Selection Guide
Synchronization Primitive
Applicable Scenario
Performance Characteristics
Mutex
Write‑heavy operations
Simple and direct
RWMutex
Read‑many, write‑few
High read concurrency
Map
Concurrent map usage
Optimized for reads
Pool
Object reuse
Reduces GC pressure
Best Practices
Lock granularity: keep critical sections as small as possible.
Avoid deadlocks by maintaining a consistent lock acquisition order.
Performance monitoring: use pprof to analyze lock contention.
Choose the appropriate synchronization primitive based on the specific scenario.
Conclusion
The sync package provides a rich set of synchronization primitives that form the foundation of Go's concurrency model. By understanding the characteristics and suitable use‑cases of each primitive, you can build efficient and safe concurrent programs; there is no one‑size‑fits‑all solution.
php中文网 Courses
php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.