Go's defining motto is: "Do not communicate by sharing memory; instead, share memory by communicating". While channels are architecturally elegant for coordinating pipelines and ownership handoffs, treating channels as a universal hammer for variable synchronization introduces severe performance bottlenecks under high contention.
1. Architectural Realities of Synchronization Primitives
- Channels: Complex heap structures (
runtime.hchan) incorporating circular ring buffers, internal lock mechanisms, and wait queues (runtime.sudog). - sync.Mutex: Fast-path atomic CAS (Compare-And-Swap) backed by a slow-path adaptive spinning and runtime semaphore sleep.
- sync/atomic: Direct hardware-level CPU atomic instructions (e.g.
LOCK CMPXCHGon x86-64 orLDREX/STREXon ARM). Zero context switches or OS thread preemption.
2. Empirical Benchmark Comparison
| Primitive | Latency (ns/op) | Heap Allocs (B/op) | Ideal Use Case |
|---|---|---|---|
| sync/atomic | ~3.8 ns | 0 B | Metrics, counters, state flags |
| sync.Mutex | ~14.2 ns | 0 B | Multi-field struct mutations |
| sync.RWMutex (90% Read) | ~8.5 ns | 0 B | Read-heavy configuration caches |
| Buffered Channel | ~68.4 ns | 0 B | Producer-consumer coordination |
| Unbuffered Channel | ~145.0 ns | 0 B | Synchronous handoffs & stop signals |
Advertisement / Sponsored