Go
Go sync
Go sync package primitives, locks, once initialization, wait groups, pools, maps, and condition variables reference.
Go sync Package Developer Guide
Import
import "sync"sync.Mutex — Mutual Exclusion Lock
var mu sync.Mutex
mu.Lock() // Acquire the lock (blocks if already held)
mu.Unlock() // Release the lock
mu.TryLock() // Try to acquire lock; returns false if already held (Go 1.18+)Basic usage pattern
var (
mu sync.Mutex
counter int
)
func increment() {
mu.Lock()
defer mu.Unlock() // Always defer Unlock to avoid deadlocks
counter++
}sync.RWMutex — Reader/Writer Mutex
var rw sync.RWMutex
rw.Lock() // Acquire write lock (exclusive)
rw.Unlock() // Release write lock
rw.RLock() // Acquire read lock (shared; multiple readers allowed)
rw.RUnlock() // Release read lock
rw.TryLock() // Non-blocking write lock attempt (Go 1.18+)
rw.TryRLock() // Non-blocking read lock attempt (Go 1.18+)Usage pattern
var (
rw sync.RWMutex
cache = map[string]string{}
)
func read(key string) string {
rw.RLock()
defer rw.RUnlock()
return cache[key]
}
func write(key, val string) {
rw.Lock()
defer rw.Unlock()
cache[key] = val
}sync.WaitGroup — Wait for Goroutines to Finish
var wg sync.WaitGroup
wg.Add(n) // Increment counter by n (call before launching goroutine)
wg.Done() // Decrement counter by 1 (call inside goroutine when finished)
wg.Wait() // Block until counter reaches 0Usage pattern
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Println("worker", id)
}(i)
}
wg.Wait() // Block until all 5 goroutines call Done()
fmt.Println("all done")sync.Once — Execute a Function Exactly Once
var once sync.Once
once.Do(fn) // Calls fn only on the first invocation; subsequent calls are no-opsUsage pattern — lazy singleton initialization
var (
once sync.Once
instance *DB
)
func GetDB() *DB {
once.Do(func() {
instance = &DB{} // Expensive init runs only once
})
return instance
}sync.Map — Concurrent-Safe Map
var m sync.Map
m.Store(key, value) // Set a key-value pair
val, ok := m.Load(key) // Get value; ok is false if key absent
val, loaded := m.LoadOrStore(key, val) // Store if absent; return existing if present
val, loaded := m.LoadAndDelete(key) // Load and then delete the key atomically
m.Delete(key) // Delete a key
m.Range(func(k, v any) bool { ... }) // Iterate all entries; return false to stop
m.Swap(key, value) // Store and return previous value (Go 1.20+)
m.CompareAndSwap(key, old, new) // Swap only if current value == old (Go 1.20+)
m.CompareAndDelete(key, old) // Delete only if current value == old (Go 1.20+)Usage pattern
var m sync.Map
m.Store("hits", 0)
// Concurrent reads and writes are safe without extra locking
go func() { m.Store("hits", 42) }()
go func() {
if v, ok := m.Load("hits"); ok {
fmt.Println(v)
}
}()
// Iterate all keys
m.Range(func(k, v any) bool {
fmt.Printf("%v => %v\n", k, v)
return true // continue iteration
})When to use: Mostly-read maps with infrequent writes, or many goroutines writing disjoint keys. For heavy mixed workloads prefer
map+sync.RWMutex.
sync.Pool — Reusable Object Pool
pool := &sync.Pool{
New: func() any { return new(bytes.Buffer) }, // Called when pool is empty
}
obj := pool.Get() // Retrieve object from pool (or call New)
pool.Put(obj) // Return object to pool for reuseUsage pattern — reducing allocations
var bufPool = sync.Pool{
New: func() any { return new(bytes.Buffer) },
}
func process(data string) string {
buf := bufPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset() // Always reset before returning!
bufPool.Put(buf)
}()
buf.WriteString(data)
return buf.String()
}Note: Objects in the pool may be evicted by the GC at any time. Never store objects with finalization logic or file handles in a Pool.
sync.Cond — Condition Variable
cond := sync.NewCond(&mu) // Create Cond bound to a Locker (Mutex or RWMutex)
cond.Wait() // Atomically unlock mu and suspend; re-locks on wake
cond.Signal() // Wake one goroutine waiting on cond
cond.Broadcast() // Wake all goroutines waiting on condUsage pattern — producer/consumer
var (
mu sync.Mutex
cond = sync.NewCond(&mu)
ready bool
)
// Consumer goroutine
go func() {
mu.Lock()
for !ready { // Always loop — spurious wakeups can occur
cond.Wait()
}
fmt.Println("got signal!")
mu.Unlock()
}()
// Producer goroutine
go func() {
mu.Lock()
ready = true
cond.Broadcast() // Wake all waiting goroutines
mu.Unlock()
}()Common Patterns & Advanced Usage
Pattern: Protecting a Struct with an Embedded Mutex
type SafeCounter struct {
mu sync.Mutex
count int
}
func (c *SafeCounter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
func (c *SafeCounter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}Pattern: Read-Heavy Cache with RWMutex
type Cache struct {
mu sync.RWMutex
items map[string]any
}
func (c *Cache) Get(key string) (any, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
v, ok := c.items[key]
return v, ok
}
func (c *Cache) Set(key string, val any) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = val
}Pattern: Worker Pool with WaitGroup + Channel
jobs := make(chan int, 100)
var wg sync.WaitGroup
// Launch workers
for w := 0; w < 4; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := range jobs {
process(j)
}
}()
}
// Send jobs
for j := 0; j < 20; j++ {
jobs <- j
}
close(jobs)
wg.Wait()Pattern: Double-Checked Locking with sync.Once
// Avoid: manually layering Once + Mutex — sync.Once already handles this safely.
// Prefer:
var (
once sync.Once
client *http.Client
)
func getClient() *http.Client {
once.Do(func() {
client = &http.Client{Timeout: 10 * time.Second}
})
return client
}Pattern: Timed Lock Attempt (simulated TryLock loop)
// TryLock with deadline (Go 1.18+)
deadline := time.Now().Add(500 * time.Millisecond)
for time.Now().Before(deadline) {
if mu.TryLock() {
defer mu.Unlock()
// critical section
break
}
time.Sleep(5 * time.Millisecond)
}Pitfalls & Best Practices
DO DON'T
───────────────────────────────────────── ──────────────────────────────────────────
Always defer Unlock after Lock Call Unlock in a separate if-branch (easy to miss)
Check the condition in a for loop in Wait Use if instead of for before cond.Wait()
Reset Pool objects before Put Return dirty objects to the pool
Use RWMutex for read-heavy workloads Always reach for Mutex (readers block readers)
Pass *sync.Mutex by pointer Copy a Mutex/WaitGroup after first use
Call wg.Add before launching goroutine Call wg.Add inside the goroutine (race condition)
Use sync.Once for one-time initialization Re-initialize manually with flags + MutexQuick Reference Card
| Type | Use Case | Key Methods |
|---|---|---|
Mutex | Exclusive access to shared resource | Lock, Unlock, TryLock |
RWMutex | Many readers, few writers | RLock, RUnlock, Lock, Unlock |
WaitGroup | Wait for N goroutines to complete | Add, Done, Wait |
Once | Exactly-once initialization | Do |
Map | Concurrent-safe map without external locking | Store, Load, Delete, Range |
Pool | Reuse temporary objects to reduce GC pressure | Get, Put |
Cond | Goroutines waiting on a condition to become true | Wait, Signal, Broadcast |