โš›๏ธsync/atomicLESSON

sync/atomic

Why atomics?

Mutexes protect arbitrary code sections but carry overhead: a sync.Mutex lock/unlock pair costs ~20โ€“50 ns even uncontended. For a single shared integer โ€” a hit counter, a flag, an index โ€” atomic operations provide lock-free synchronisation at the hardware level.

The typed atomic API (Go 1.19+)

Go 1.19 introduced value types in sync/atomic that are safe and ergonomic:

atomic.Int64, atomic.Int32, atomic.Uint64, atomic.Bool, atomic.Pointer[T] โ€” all follow the same pattern.

atomic.Bool โ€” single flag

No mutex needed for a single boolean flag.

Compare-and-swap (CAS)

CAS atomically does: if current == old, set to new and return true; else return false.

CAS is the building block for lock-free data structures and single-flight deduplication.

atomic.Pointer[T]

Atomically swap a pointer to any value โ€” useful for hot config reloading:

When to use atomics vs mutexes

Use atomics whenUse mutexes when
Single integer / bool / pointerProtecting a struct with multiple fields
Extremely high-frequency updatesComplex conditional logic across fields
Building lock-free algorithmsCorrectness is more important than peak perf

Default to mutexes. Reach for atomics only when profiling shows mutex contention is a bottleneck.

The old function-based API

Before Go 1.19, the API used standalone functions. You'll see this in older code:

The newer value types (atomic.Int64) are safer โ€” they cannot be accidentally copied.

Knowledge Check

When should you prefer sync/atomic over sync.Mutex?

What does atomic.Int64.CompareAndSwap(old, new) do?

Why are the typed atomics (atomic.Int64) safer than the function-based API (atomic.AddInt64(&n, 1))?