All Benchmarks

Go Benchmark

Mutex vs RWMutex vs Atomic

Which synchronization primitive should guard a shared value?

mutexrwmutexatomicsyncconcurrencylocking

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.

linux/amd64AMD Ryzen 9 9950X3D 16-Core Processorbenchmarks/mutex-vs-rwmutex-vs-atomic
Compare atCPUs

1 CPU

Read

Fastest

Atomic

4.5 ns/op

Slowest

Mutex

10.77 ns/op · 2.4x slower

Write

Fastest

Atomic

7.06 ns/op

Slowest

RW Mutex

18.29 ns/op · 2.6x slower

32 CPUs

Read

Fastest

Atomic

14.8 ns/op

Slowest

Mutex

37.28 ns/op · 2.5x slower

Write

Fastest

Atomic

20.26 ns/op

Slowest

RW Mutex

47.32 ns/op · 2.3x slower

Performance Comparison (lower is better)
CPU:

Atomic #

Fastest (Read)Fastest (Write)

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.

Performance (lower is better)
CPU:
func BenchmarkAtomic_read(b *testing.B) {
	var value atomic.Int64
	value.Store(42)

	b.RunParallel(func(pb *testing.PB) {
		var total int64
		for pb.Next() {
			total += value.Load()
		}
		sink.Add(total)
	})
}

func BenchmarkAtomic_write(b *testing.B) {
	var value atomic.Int64

	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			value.Add(1)
		}
	})
	sink.Add(value.Load())
}
1 CPU

Read

2.4xfaster(139%)thanMutex
2.3xfaster(131%)thanRW Mutex

Write

1.5xfaster(48%)thanMutex
2.6xfaster(159%)thanRW Mutex
32 CPUs

Read

2.5xfaster(152%)thanMutex
1.5xfaster(50%)thanRW Mutex

Write

2.1xfaster(109%)thanMutex
2.3xfaster(134%)thanRW Mutex

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.

Performance (lower is better)
CPU:
func BenchmarkRWMutex_read(b *testing.B) {
	var mu sync.RWMutex
	value := int64(42)

	b.RunParallel(func(pb *testing.PB) {
		var total int64
		for pb.Next() {
			mu.RLock()
			total += value
			mu.RUnlock()
		}
		sink.Add(total)
	})
}

func BenchmarkRWMutex_write(b *testing.B) {
	var mu sync.RWMutex
	var value int64

	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			mu.Lock()
			value++
			mu.Unlock()
		}
	})
	sink.Add(value)
}
1 CPU

Read

2.3xslower(131%)thanAtomic
1xfaster(3%)thanMutex

Write

2.6xslower(159%)thanAtomic
1.7xslower(75%)thanMutex
32 CPUs

Read

1.5xslower(50%)thanAtomic
1.7xfaster(68%)thanMutex

Write

2.3xslower(134%)thanAtomic
1.1xslower(12%)thanMutex

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.

Performance (lower is better)
CPU:
func BenchmarkMutex_read(b *testing.B) {
	var mu sync.Mutex
	value := int64(42)

	b.RunParallel(func(pb *testing.PB) {
		var total int64
		for pb.Next() {
			mu.Lock()
			total += value
			mu.Unlock()
		}
		sink.Add(total)
	})
}

func BenchmarkMutex_write(b *testing.B) {
	var mu sync.Mutex
	var value int64

	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			mu.Lock()
			value++
			mu.Unlock()
		}
	})
	sink.Add(value)
}
1 CPU

Read

2.4xslower(139%)thanAtomic
1xslower(3%)thanRW Mutex

Write

1.5xslower(48%)thanAtomic
1.7xfaster(75%)thanRW Mutex
32 CPUs

Read

2.5xslower(152%)thanAtomic
1.7xslower(68%)thanRW Mutex

Write

2.1xslower(109%)thanAtomic
1.1xfaster(12%)thanRW Mutex

Contributors