Go Benchmark
Mutex vs RWMutex vs Atomic
Which synchronization primitive should guard a shared value?
Guarding a single shared value is the "hello world" of concurrency, and Go gives you three tools for it: sync.Mutex, sync.RWMutex, and sync/atomic. This benchmark runs concurrent readers and writers (via b.RunParallel) against an int64 guarded by each primitive. Watch how the results change with the CPU count: RWMutex lets readers proceed in parallel but pays extra bookkeeping for it, plain Mutex serializes everything but is cheaper per operation, and atomics sidestep locking entirely. The common advice to "just use RWMutex for read-heavy workloads" is worth checking against numbers — its read path can cost more than an uncontended Mutex.
An atomic.Int64 accessed with Load and Add. No lock at all: reads compile to a plain memory load with ordering guarantees, and writes to a single hardware instruction. Unbeatable for simple counters and flags, but it only works when the shared state is a single word — it cannot protect multi-field invariants.
RW Mutex #
Slowest (Write)A sync.RWMutex with RLock for reads and Lock for writes. Multiple readers may hold the lock simultaneously, which helps when reads are long or heavily parallel — but the reader accounting makes each individual RLock/RUnlock more expensive than a plain mutex, and writers must wait for all readers to drain.
Mutex #
Slowest (Read)A plain sync.Mutex around both reads and writes. Only one goroutine can hold it at a time, so concurrent readers serialize — but the lock itself is very lean (a single atomic CAS in the uncontended fast path), which keeps per-operation cost low.
Contributors