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
", 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Gosync
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

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.