Atomics Complete
Atomics Complete
Overview
sync/atomic provides lock-free operations that the CPU can perform safely under concurrency. Composition of atomics is not atomic—that distinction separates counters from broken “clever” state machines.
Non-atomic increment
total++ // read-modify-write: can lose updates
// 5 Gs × 10000 → often total < 50000G1 reads 42
G2 reads 42
G1 writes 43
G2 writes 43 // lost update
Atomic types and ops
| Type | Ops |
|---|---|
atomic.Bool |
Load, Store, Swap, CompareAndSwap |
atomic.Int32/64, Uint… |
+ Add, And, Or (1.23+) |
atomic.Pointer[T] |
typed pointer |
atomic.Value |
any type (consistent concrete type) |
var total atomic.Int32
total.Add(1)
fmt.Println(total.Load())
// CAS
var flag atomic.Bool
if flag.CompareAndSwap(false, true) {
// first closer wins
}Pass atomics by pointer (or embed in structs used via pointer).
Composition traps
// Guaranteed sum of +1/+1 even with sleep between (sequence-independent Adds)
counter.Add(1)
sleep()
counter.Add(1)
// NOT a reliable algorithm: Load then branch then Add
if counter.Load()%2 == 0 {
counter.Add(1)
} else {
counter.Add(2)
}
// No data race, but race CONDITION → unpredictable resultregions: OK | Race condition without data race
flow:
[Br]
|
v
[Ad]
Bulletproof composite update: mutex around the whole sequence, or a careful CAS loop that retries.
CAS gate (mutex alternative for once)
type Gate struct{ closed atomic.Bool }
func (g *Gate) Close() {
if !g.closed.CompareAndSwap(false, true) {
return // already closed
}
// free resources once
}Great for “first caller wins / early exit”. Not a substitute when waiters must block until unlock.
When to use atomics
| Use | Prefer |
|---|---|
| Counters, flags | atomics |
| Multi-field invariants | mutex |
| Hand-off of ownership | channel |
| Complex lock-free structures | expert only + heavy tests |
Runnable example
go run -race .package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var total atomic.Int32
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Go(func() {
for j := 0; j < 10000; j++ {
total.Add(1)
}
})
}
wg.Wait()
fmt.Println("total", total.Load())
}Expected: total 50000 and race-clean.
Try next: Implement a CAS loop that doubles a value only if still equal to an observed snapshot.