Skip to content
sync.Mutex and sync.WaitGroup
step 1/6

Reading — step 1 of 6

Learn

~2 min readConcurrency Patterns

Channels are Go's preferred way to coordinate goroutines. But sometimes you genuinely have shared mutable state (a cache, a counter, a connection pool) and need traditional locks. The sync package provides them.

sync.Mutex — mutual exclusion

go

Lock() blocks until the mutex is free. Unlock() releases it. The defer pattern ensures unlock runs even if the body panics.

sync.RWMutex — many readers OR one writer

For read-heavy workloads, RWMutex lets unlimited concurrent reads but exclusive writes:

go

RLock for read-only access, Lock for write. Multiple RLocks can coexist; a Lock waits for all readers and excludes everything.

sync.WaitGroup — wait for N goroutines

Launching N goroutines and waiting for all to finish:

go

Three calls:

  1. Add(n) — register that n goroutines are about to start.
  2. Done() — register one as finished. Conventionally defer wg.Done() at the top of the goroutine.
  3. Wait() — block until count reaches zero.

sync.Once — one-time initialization

Lazy initialization that runs exactly once even from many goroutines:

go

Calling getDB from 100 goroutines runs openDB exactly once.

When to use channels vs locks

Go's mantra: "Don't communicate by sharing memory; share memory by communicating." In practice:

  • Channels — for coordination, pipelines, signaling. "Send work to a worker."
  • Mutex — for protecting actual shared state. A counter, a cache, a config.

Both tools have their place. Channels for control flow; locks for shared data.

Common mistakes

  • Forgetting to unlock — every Lock() needs an Unlock(). Use defer immediately.
  • Calling wg.Add inside the goroutine — race: Wait() might run before Add. Always Add BEFORE go.
  • Holding a lock during a long operation — blocks every other goroutine waiting. Lock briefly, do work, unlock.
  • Recursive locking — calling Lock() while the same goroutine already holds the lock deadlocks. RWMutex does NOT fix this: a nested RLock() deadlocks too once a writer is waiting. Restructure so each acquisition is released first.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…