2026-09-01
Every lock-free data structure you've written — every std::atomic::compare_exchange, every Java AtomicReference.compareAndSet — bottoms out at a single instruction: CMPXCHG on x86, CAS on SPARC. Software treats it as a magic primitive. Hardware has to actually make it atomic across a coherent multi-core system, and the mechanism is surprisingly concrete.
The naive implementation locks the memory bus for the duration: assert LOCK#, do a read-modify-write, release. This is what the original Pentium's LOCK CMPXCHG literally did — every other core stalled. It works, but it doesn't scale past a handful of cores because the bus becomes the bottleneck.
Modern implementations do it entirely in the cache coherence protocol. The sequence on an Intel Skylake core:
LOCK CMPXCHG [addr], rbx with expected value in rax.rax. If equal, the store portion writes rbx and updates the cache line. If not, no store occurs but the line still stays in M.The whole operation is atomic because between the RFO and the unlock, no other core can even observe the line, let alone modify it. The coherence protocol enforces the atomicity — no explicit bus lock needed.
Concrete real-world example: a lock-free stack push using CAS on Skylake. Uncontended CAS takes ~5-6 cycles (L1 hit, line already in M state). Contended CAS with the line in another core's L1 takes ~40-60 cycles just for the RFO snoop round-trip across the ring bus. This is why uncontended lock-free code is fast and contended lock-free code often loses to a well-designed mutex — the CAS itself isn't the cost, the coherence traffic is.
Rule of thumb: a successful CAS on a line already in M state ≈ 5 cycles. A CAS on a line owned by another core on the same socket ≈ 40 cycles. A CAS on a line owned by another socket ≈ 200+ cycles. Multiply by your CAS-retry loop depth to predict lock-free algorithm performance under contention.
