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