Memory Fences and Barriers
Memory fences constrain instruction reordering and visibility of writes across cores, providing the ordering guarantees that lock-free code composes correctly on weakly ordered CPUs.
Definition
A memory fence (barrier) is a processor instruction that enforces ordering of memory operations around it: load-load, load-store, store-load, store-store combinations in acquire and release forms. Compilers and CPUs reorder memory accesses for performance; fences are the mechanism for publishing and observing data safely across threads without locks.
Context and scope
Underlies atomic operations, mutex implementations, and lock-free algorithms. Visible to application developers through acquire and release semantics, volatile ordering, and memory order choices in C++ and Rust.
Common misconceptions
- Modern CPUs make fences unnecessary — weakly ordered architectures still reorder; atomics with memory ordering embed the fences implicitly
- Volatile in every language means sequential consistency — volatile in Java and C# does; volatile in C++ means only no-optimization, with no inter-thread ordering guarantee beyond what is stated
- Fences fix data races — fences order accesses; unsynchronized conflicting accesses are still data races with undefined behavior in most language models
Anti-patterns
- Publishing a pointer with a plain store and the data with earlier plain stores, letting consumers see garbage on weak cores
- Using acquire ordering where release is needed and building a message-passing pattern that reads torn state
- Optimizing fences out of a hot path by measurement on x86 and shipping broken ARM builds
Related concepts
Related examples
Authoritative source
What it is
Cores buffer and reorder memory operations; compilers reorder them too. Two threads communicating through memory need agreement on when a write becomes visible and in what order. Fences draw those lines in the instruction stream.
How it works
Release ordering on a store guarantees prior writes are visible before the releasing store lands; acquire on a load guarantees subsequent reads see everything published before the matching release. Store-load full fences back sequentially consistent atomics. Higher-level constructs — mutexes, channels, atomics with ordering parameters — compile down to these primitives.
When to care
Writing lock-free queues, publishing initialization of shared objects, and interpreting memory order parameters on atomic APIs. Most developers consume fences through these APIs rather than emitting them directly.
Pitfalls
x86 strong ordering hides fence bugs that ARM and RISC-V then expose — test on weakly ordered hardware or emulators. Double-checked locking is the classic pattern that is broken without exactly the right acquire and release placement. Fence-heavy hot loops cost measurable throughput; prefer higher-level primitives first.