0006: Sometimes You Need Locks (sync, atomic & the Race Detector)
Date: 2026-06-14 Status: Accepted
Insight
Channels are the Go way, but they're not always the right tool. When you have a simple shared counter, a sync.Mutex is clearer than a channel. When you need to wait for N goroutines to finish, sync.WaitGroup is the standard tool. When you need a lazy one-time initialization, sync.Once. And when performance matters for simple counters, atomic operations avoid lock overhead entirely.
The race detector (go test -race) is Go's superpower: it instruments the binary to detect data races at runtime. Every cloud-native project runs CI with -race. Understanding what it catches and the false positives it can produce is essential.
This lesson should be taught as "when channels aren't the answer", it complements Lesson 0005 rather than contradicting it. The mental model: channels for coordination and data flow, sync primitives for protecting shared state.
Consequences
- Lesson 0006: sync.Mutex, sync.RWMutex, sync.WaitGroup, sync.Once, atomic operations, and the race detector
- The concurrency cheat sheet from Lesson 0005 already covers sync primitives: this lesson adds context
- Exercises should include: benchmark mutex vs atomic, use WaitGroup for fan-in, use sync.Once for lazy init
- Must emphasize the race detector as a tool that should be run on every test invocation