Backend Development 8 min read

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.

php中文网 Courses
php中文网 Courses
php中文网 Courses
Deep Dive into Go's sync Package: 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.

backendConcurrencyGoSynchronizationsync
php中文网 Courses
Written by

php中文网 Courses

php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.