Go sync package primitives, locks, once initialization, wait groups, pools, maps, and condition variables reference.
Go sync Package Developer Guide
Section titled “Go sync Package Developer Guide”Import
Section titled “Import”import "sync"sync.Mutex — Mutual Exclusion Lock
Section titled “sync.Mutex — Mutual Exclusion Lock”var mu sync.Mutex
mu.Lock() // Acquire the lock (blocks if already held)mu.Unlock() // Release the lockmu.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
Section titled “sync.RWMutex — Reader/Writer Mutex”var rw sync.RWMutex
rw.Lock() // Acquire write lock (exclusive)rw.Unlock() // Release write lockrw.RLock() // Acquire read lock (shared; multiple readers allowed)rw.RUnlock() // Release read lockrw.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
Section titled “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
Section titled “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
Section titled “sync.Map — Concurrent-Safe Map”var m sync.Map
m.Store(key, value) // Set a key-value pairval, ok := m.Load(key) // Get value; ok is false if key absentval, loaded := m.LoadOrStore(key, val) // Store if absent; return existing if presentval, loaded := m.LoadAndDelete(key) // Load and then delete the key atomicallym.Delete(key) // Delete a keym.Range(func(k, v any) bool { ... }) // Iterate all entries; return false to stopm.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 lockinggo func() { m.Store("hits", 42) }()go func() { if v, ok := m.Load("hits"); ok { fmt.Println(v) }}()
// Iterate all keysm.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
Section titled “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
Section titled “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 wakecond.Signal() // Wake one goroutine waiting on condcond.Broadcast() // Wake all goroutines waiting on condUsage pattern — producer/consumer
var ( mu sync.Mutex cond = sync.NewCond(&mu) ready bool)
// Consumer goroutinego func() { mu.Lock() for !ready { // Always loop — spurious wakeups can occur cond.Wait() } fmt.Println("got signal!") mu.Unlock()}()
// Producer goroutinego func() { mu.Lock() ready = true cond.Broadcast() // Wake all waiting goroutines mu.Unlock()}()Common Patterns & Advanced Usage
Section titled “Common Patterns & Advanced Usage”Pattern: Protecting a Struct with an Embedded Mutex
Section titled “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
Section titled “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
Section titled “Pattern: Worker Pool with WaitGroup + Channel”jobs := make(chan int, 100)var wg sync.WaitGroup
// Launch workersfor w := 0; w < 4; w++ { wg.Add(1) go func() { defer wg.Done() for j := range jobs { process(j) } }()}
// Send jobsfor j := 0; j < 20; j++ { jobs <- j}close(jobs)
wg.Wait()Pattern: Double-Checked Locking with sync.Once
Section titled “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)
Section titled “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
Section titled “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 poolUse RWMutex for read-heavy workloads Always reach for Mutex (readers block readers)Pass *sync.Mutex by pointer Copy a Mutex/WaitGroup after first useCall 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
Section titled “Quick 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 |