Master Go Concurrency with sync: Mutex, RWMutex, WaitGroup, Once, Cond & Pool

Learn how Go's sync package provides essential concurrency primitives—Mutex, RWMutex, WaitGroup, Once, Cond, and Pool—through clear examples and best‑practice patterns that prevent data races, deadlocks, and performance pitfalls, enabling safe, elegant, and efficient goroutine coordination.

Code Wrench
Code Wrench
Code Wrench
Master Go Concurrency with sync: Mutex, RWMutex, WaitGroup, Once, Cond & Pool

Why Use sync?

Many Go beginners write code that increments a shared counter from multiple goroutines, which leads to nondeterministic results because of data races. The sync package offers a full set of synchronization primitives to safely access shared data.

for i := 0; i < 10; i++ {
    go func() {
        counter++
    }()
}

sync.Mutex

Mutex

is the classic mutual‑exclusion lock. Only one goroutine can hold the lock at a time, ensuring exclusive access to critical sections.

var mu sync.Mutex
var counter int

func increment() {
    mu.Lock()
    counter++
    mu.Unlock()
}
Tip: Use defer mu.Unlock() right after locking to guarantee the unlock even if a panic occurs.

sync.RWMutex

When the workload is read‑heavy (e.g., caches or configuration tables), RWMutex allows multiple readers simultaneously while still enforcing exclusive access for writers.

var mu sync.RWMutex
var data = make(map[string]string)

func read(key string) string {
    mu.RLock()
    defer mu.RUnlock()
    return data[key]
}

func write(key, value string) {
    mu.Lock()
    data[key] = value
    mu.Unlock()
}

Imagine a library where many readers can browse books at once, but when the librarian adds a new book, everyone must leave.

sync.WaitGroup

WaitGroup

acts as a counter that makes the main goroutine wait until all spawned goroutines finish.

var wg sync.WaitGroup

for i := 0; i < 3; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        fmt.Println("Worker", id, "done")
    }(i)
}

wg.Wait()
fmt.Println("All workers finished")

Key methods: Add(n): declare how many tasks to wait for. Done(): signal completion of one task. Wait(): block until the counter reaches zero.

sync.Once

Once

guarantees that a piece of code runs only a single time, which is useful for loading configuration, initializing connection pools, or implementing singletons.

var once sync.Once

func initConfig() {
    fmt.Println("Config loaded")
}

func main() {
    for i := 0; i < 3; i++ {
        go func() {
            once.Do(initConfig)
        }()
    }
}

sync.Cond

Cond

provides a condition variable for goroutines to wait until a specific condition becomes true, then be signaled or broadcast.

var ready = false
var mu sync.Mutex
var cond = sync.NewCond(&mu)

func worker() {
    mu.Lock()
    for !ready {
        cond.Wait()
    }
    fmt.Println("Worker proceeding")
    mu.Unlock()
}

func main() {
    go worker()
    time.Sleep(time.Second)
    mu.Lock()
    ready = true
    cond.Signal()
    mu.Unlock()
}

Core methods: Wait(): block until the condition is signaled. Signal(): wake up one waiting goroutine. Broadcast(): wake up all waiting goroutines.

sync.Pool

Pool

caches temporary objects to reduce allocation overhead and GC pressure, ideal for high‑frequency creation and reuse scenarios such as bytes.Buffer.

var pool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

func main() {
    buf := pool.Get().(*bytes.Buffer)
    buf.WriteString("hello")
    fmt.Println(buf.String())
    buf.Reset()
    pool.Put(buf)
}

Note: Pool entries are cleared during garbage collection, so it should not be used for storing long‑term important data.

Conclusion

Mastering these synchronization primitives lets you write Go concurrent programs that are safe, orderly, and high‑performance. Think of goroutines as musicians, channels as rhythm, and the sync package as the conductor that keeps the whole orchestra in tune.

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.

concurrencyGomutexRWMutexsyncWaitGroupPoolOnce
Code Wrench
Written by

Code Wrench

Focuses on code debugging, performance optimization, and real-world engineering, sharing efficient development tips and pitfall guides. We break down technical challenges in a down-to-earth style, helping you craft handy tools so every line of code becomes a problem‑solving weapon. 🔧💻

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.