Day 064 — sync.Mutex & RWMutex¶
Month 3 · Week 2 · ⬅ Day 063 · Day 065 ➡ · Journal index
🎯 Learning Objective¶
Protect shared mutable state with sync.Mutex and, when reads dominate, sync.RWMutex, without introducing data races or deadlocks.
📚 Topics¶
sync.MutexLock/Unlock and the critical sectionsync.RWMutexRLock/RUnlock vs Lock/Unlock- "Share memory by communicating" vs guarding memory with a lock
📖 Reading / Sources¶
- Learning Go (Bodner) ch.10 — When to use mutexes vs channels
-
syncpackage docs - Go blog — Share Memory By Communicating
📝 Notes¶
- A [[data-race]] is two goroutines accessing the same memory concurrently with at least one write; the result is undefined behaviour, not just a wrong number.
counter++is load+add+store — three steps that interleave badly. - A [[mutex]] (
sync.Mutex) makes a [[critical-section]] mutually exclusive:Lock()blocks until the lock is free,Unlock()releases it. Pair them withdefer Unlock()immediately afterLock()so early returns and panics still release. - The zero value of a
Mutex/RWMutexis an unlocked, ready-to-use lock — no constructor needed. Keep the lock next to the data it guards, often as an unexported struct field. - [[rwmutex]] (
sync.RWMutex) allows many concurrentRLockholders or oneLockholder. Use it only when reads vastly outnumber writes; under write-heavy load it can be slower than a plainMutexdue to bookkeeping. - A
Mutexis not reentrant: locking it twice in the same goroutine deadlocks. It is also not associated with a goroutine — any goroutine may Unlock, though idiomatically the locker unlocks. - Never copy a mutex after first use (it copies the lock state);
go vetflags this. Pass*TwhereTembeds a mutex. This is the [[no-copy]] rule. - Channels coordinate ownership transfer; mutexes guard shared state in place. Rule of thumb: a channel for handing data between goroutines, a mutex for a struct field many goroutines read/write.
💻 Code Examples¶
type SafeCounter struct {
mu sync.Mutex // guards n; lock lives next to the data it protects
n int
}
func (c *SafeCounter) Inc() {
c.mu.Lock()
defer c.mu.Unlock() // survives early return / panic
c.n++
}
Full code:
examples/month-03/mutex/main.go· Run:go run ./examples/month-03/mutex· Race check (after deleting the lock):go run -race ./examples/month-03/mutex
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
| Thread-safe counter (Mutex/RWMutex) | ✅ | exercises/month-03/week-2/safecounter |
🐛 Mistakes Made¶
- Returned early from a method before
Unlock(nodefer) → nextLockblocked forever. Moved todefer c.mu.Unlock(). - Wrote
c2 := *cto "snapshot" a counter and copied the mutex with it —go vetwarned "assignment copies lock value". Passed*SafeCounterinstead.
❓ Open Questions¶
- When is
RWMutexactually a win vs a plainMutex? (Needs a benchmark with a realistic read/write ratio.)
🧠 Active Recall (answer without looking)¶
- Q: Why is
counter++unsafe across goroutines without a lock?A
It compiles to load→add→store; two goroutines can both load the same old value, so increments are lost. It is also a data race (undefined behaviour), not merely a lost update.
2. Q: What does go vet complain about if you pass a struct containing a sync.Mutex by value? A
"assignment/argument copies lock value" — copying a mutex after use breaks mutual exclusion because the copy has independent lock state. Pass a pointer.
🪶 Feynman Reflection¶
A mutex is a single key to a room: only the goroutine holding the key may be inside the critical section, so the shared variable is never seen half-updated. An RWMutex is a library reading room — any number of readers at once, but the room is cleared for a single writer.
🕳️ Knowledge Gaps¶
- Lock contention profiling (
go test -bench+-mutexprofile) — revisit during the optimisation month.
✅ Summary¶
I can guard shared state with sync.Mutex (and RWMutex for read-heavy access), always defer Unlock, keep the lock beside its data, and avoid copying locks.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 065:
sync.WaitGroupin depth — lock-free result collection and error aggregation withouterrgroup.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: feat(examples): sync.Mutex and RWMutex safe counter (day 064)