Day 067 — Atomic Operations¶
Month 3 · Week 2 · ⬅ Day 066 · Day 068 ➡ · Journal index
🎯 Learning Objective¶
Use sync/atomic for lock-free counters and flags, write a correct compare-and-swap (CAS) retry loop, and know the strict limits of atomics versus a mutex.
📚 Topics¶
- The typed atomics (
atomic.Int64,atomic.Bool,atomic.Pointer[T]) — Go 1.19+ Add,Load,Store,Swap,CompareAndSwap- The CAS retry-loop pattern for lock-free updates
📖 Reading / Sources¶
-
sync/atomicdocs - Learning Go (Bodner) ch.10 — atomics section
- The Go Memory Model — synchronisation
📝 Notes¶
- An [[atomic-operation]] is indivisible: no goroutine can observe it half-applied.
atomic.Int64.Add(1)performs load+add+store as one step, so a contended counter lands on the exact total with no mutex. - Prefer the Go 1.19+ atomic value types (
atomic.Int32/Int64/Uint64/Bool/Pointer[T]/Value) over the old free functions (atomic.AddInt64(&x, …)). The types are self-aligned, can't be mixed-width by mistake, andgo vetcatches copies. - The zero value of
atomic.Int64etc. is ready to use (value 0). Like a mutex, never copy one after first use — it's a [[no-copy]] type; pass a pointer. - [[compare-and-swap]] (
CompareAndSwap(old, new) bool): set tonewonly if the current value still equalsold; returns whether it succeeded. Exactly one of many racing goroutines wins aCAS(0,1)— the basis of lock-free "claim once" and leader election. - CAS retry loop is the lock-free update pattern:
for { cur := x.Load(); next := f(cur); if x.CompareAndSwap(cur, next) { break } }. If another goroutine movedcurbetween Load and CAS, the CAS fails and you retry with the fresh value. - Atomics protect one word. They give you a safe single counter/flag/pointer — not a way to keep two fields consistent together. Multi-field invariants still need a
Mutex. - Mixing atomic and plain access to the same variable is a data race. Once a variable is atomic, every access must go through atomic methods.
go run -racewill catch violations. atomic.Value/atomic.Pointer[T]let you atomically swap a whole immutable snapshot (e.g. a config struct) — readersLoad, a writerStorea new pointer; great for read-mostly config hot-swapping.
💻 Code Examples¶
// Lock-free "store the running max" via a CAS retry loop.
var hi atomic.Int64
func storeMax(candidate int64) {
for {
cur := hi.Load()
if candidate <= cur {
return // already >= candidate
}
if hi.CompareAndSwap(cur, candidate) {
return // we won the race
}
// CAS failed: cur moved under us — reload and retry.
}
}
Full code:
examples/month-03/atomic/main.go· Run:go run ./examples/month-03/atomic
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
| One-shot Gate + MaxTracker (CAS) | ✅ | exercises/month-03/week-2/atomicgate |
🐛 Mistakes Made¶
- Wrote
cur := hi.Load(); hi.Store(candidate)instead of a CAS — two goroutines both read a smallercurand the larger value got clobbered. Switched toCompareAndSwapin a loop. - Read an atomic counter with a plain field access in one spot →
-raceflagged it. Made every access atomic.
❓ Open Questions¶
- When does a CAS spin loop become worse than a mutex under heavy contention? (Spinning wastes CPU when contention is high; mutexes can park the goroutine — benchmark to find the crossover.)
🧠 Active Recall (answer without looking)¶
- Q: What does
CompareAndSwap(old, new)do and return?A
It atomically sets the value to new only if it currently equals old, and returns true if it made the swap, false otherwise. The retry loop reloads and tries again on false.
2. Q: You guard a counter with atomic.Int64 but read it once with a plain access. Problem? A
Yes — mixing atomic and non-atomic access to the same variable is a data race (undefined behaviour). Every access must use atomic methods; -race will report it.
🪶 Feynman Reflection¶
An atomic is a single value the hardware can update in one unbreakable tick, so a counter never loses an increment without any lock. Compare-and-swap is "change it only if nobody changed it since I looked" — and if someone did, you look again and retry. That tiny primitive is enough to build lock-free counters, flags, and leader election.
🕳️ Knowledge Gaps¶
- Memory-ordering guarantees of atomics (sequential consistency in Go's model) — pinned for tomorrow's memory-model day.
✅ Summary¶
I can build lock-free counters and one-shot gates with sync/atomic's typed values, write a correct CAS retry loop, and I know atomics guard one word only — multi-field invariants still need a mutex.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 068: the race detector (
-race) — how to actually catch the bugs today's rules prevent.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦🟦⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: feat(examples): sync/atomic counters and CAS loop (day 067)