The x86 LOCK Prefix: How One Byte Turns Any Read-Modify-Write Into an Atomic Operation

2026-09-11

The LOCK prefix is a single byte (0xF0) that prepends certain x86 instructions to make them atomic. It's the silicon primitive underneath every mutex, atomic counter, and lock-free data structure you've ever used. Without it, ADD [mem], 1 is a load, an add, and a store — three separate bus transactions that another core can interleave with. With it, the entire read-modify-write completes without any other core observing an intermediate state.

What LOCK actually does today: On the 486, LOCK asserted a physical #LOCK pin that froze the memory bus for the duration of the instruction. Modern CPUs almost never do that. Instead, LOCK triggers a cache line lock: the core acquires the line in Exclusive/Modified state via the MESI protocol, holds off snoop responses until the RMW completes, then releases. This is orders of magnitude cheaper than a bus lock. The old bus lock only fires as a fallback when the operand crosses a cache line boundary — a "split lock" — which on modern Intel can stall every core in the socket for microseconds.

Which instructions accept LOCK: Only RMW instructions on memory operands. ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCHG8B/16B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, XCHG. Note XCHG is implicitly locked when a memory operand is used — the LOCK prefix is redundant. This is why hand-written spinlocks often use XCHG: it's one byte shorter than LOCK CMPXCHG.

Concrete example. A C11 atomic increment:

Rule of thumb: An uncontended LOCKed op costs about 20 cycles. A contended one costs about 1 cache-line-transfer round trip per core competing — figure 40–80 ns per hop. If N cores are hammering the same counter, expect throughput to degrade as roughly O(N) latency per op. This is why per-CPU counters with periodic aggregation always beat a single LOCKed global.

The split-lock trap: If your atomic straddles two cache lines (misaligned atomic<uint64_t> at offset 60 within a 64-byte line), the CPU falls back to a real bus lock. Recent kernels detect this via #AC (alignment check) exceptions and can log or kill the offender — set split_lock_detect=fatal on the kernel command line to find them in production.

Key Takeaway: The LOCK prefix trades ~20 uncontended cycles for atomicity by holding a cache line in Modified state through a read-modify-write — cheap alone, catastrophic when contended, and disastrous when misaligned across cache lines.

All newsletters