Introduction
For simple counters and flags, the sync/atomic package provides lock-free operations that are faster than mutexes. Understanding when to use atomics versus mutexes is key to writing high-performance concurrent code.
Key Concepts
- Atomic Operation: A CPU-level operation that completes in a single, indivisible step. No other goroutine can observe a partial state.
- Compare-And-Swap (CAS): Atomically sets a value only if it currently equals an expected value. The foundation of lock-free algorithms.
- Atomic Types (Go 1.19+): Type-safe wrappers like
atomic.Int64,atomic.Bool, andatomic.Pointer[T].
Real World Context
A metrics counter in a high-throughput server (e.g., counting requests per second) should use atomic.Int64 rather than a mutex-protected int. The atomic version avoids lock contention entirely, which matters when thousands of goroutines increment the counter simultaneously.
Deep Dive
For simple counters or flags, sync/atomic is faster than Mutex:
govar ops atomic.Int64 // Increment safely ops.Add(1) // Read safely val := ops.Load() // Set value ops.Store(100) // Compare and swap ops.CompareAndSwap(old, new)
Atomic Types (Go 1.19+)
govar counter atomic.Int64 var flag atomic.Bool var ptr atomic.Pointer[MyStruct]
Legacy API (pre-1.19)
govar ops uint64 atomic.AddUint64(&ops, 1) val := atomic.LoadUint64(&ops)
When to Use Atomics
- Simple counters and flags.
- When mutex overhead is too high (verified by profiling).
- Building lock-free data structures (advanced).
Warning: Atomics are harder to use correctly than mutexes for anything beyond simple counters. When in doubt, use a mutex.
Common Pitfalls
- Using
i++and assuming it is atomic —i++involves a read, modify, and write. Two goroutines doingi++simultaneously cause a data race. Useatomic.Int64.Add(1)instead. - Mixing atomic and non-atomic access — Every access to a shared variable must be atomic. Even a single non-atomic read creates a data race.
Best Practices
- Prefer the typed API (Go 1.19+) —
atomic.Int64is safer and more readable thanatomic.AddInt64(&counter, 1). - Use atomics only for simple values — For compound operations (check-then-act), use a mutex.
Summary
sync/atomicprovides lock-free operations for counters and flags.- Go 1.19+ atomic types (
atomic.Int64,atomic.Bool) are the preferred API. i++is NOT atomic — always useatomic.Addfor shared counters.- Use mutexes for compound operations; use atomics only for simple values.
Code Examples
go
import "sync/atomic"
var counter atomic.Int64
func increment() {
counter.Add(1)
}
func getCount() int64 {
return counter.Load()
}