Memory Ordering
A CPU's memory model and Rust's C11-derived memory ordering model define which values an atomic read on one thread can observe when multiple threads read and write shared memory across multi-core caches and store buffers.
Hardware Reality: Why Memory Operations Appear Out of Order
Modern CPUs do not read and write main RAM directly on every instruction. Each CPU core has a private store buffer and a hierarchy of L1/L2 caches:
- On x86_64 (Total Store Order — TSO): Stores are never reordered with other stores, and loads are never reordered with other loads, but a load can be reordered before an earlier store to a different address (because the store sits in the core's store buffer while the load hits the L1 cache immediately!).
- On AArch64 (ARM64) and RISC-V (Weakly Ordered): Both loads and stores to different addresses can be reordered freely by the hardware unless explicit acquire/release instructions (
ldar,stlr) or memory barrier fences (dmb) are emitted!
CPU Core Store Buffer Reordering (Why `Relaxed` Can Observe Stale Data):
Core 0 (Writer) Core 1 (Reader)
--------------- ---------------
DATA = 42; --> [ Store Buffer ] while !READY.load(Relaxed) {}
READY = true; --> (Flushed first!) -> Sees READY == true!
Reads DATA from L1 cache -> Sees 0 (Stale)!
How Release and Acquire Form a Memory Barrier Synchronizes-With Edge
When Thread A performs a store with Ordering::Release (or Ordering::AcqRel / Ordering::SeqCst) to an atomic variable M, and Thread B performs a load with Ordering::Acquire (or Ordering::AcqRel / Ordering::SeqCst) from the same variable M that observes the value written by Thread A:
- Synchronizes-With: Thread A's
Releasestore synchronizes-with Thread B'sAcquireload. - Happens-Before: Every memory write (whether atomic,
Relaxed, or non-atomic inside anUnsafeCell) executed by Thread A before theReleasestore is guaranteed to be visible to Thread B after theAcquireload!
Thread A (Producer) Thread B (Consumer)
------------------- -------------------
1. non_atomic_buffer = "hello";
2. metric.store(10, Relaxed);
======== [ Release Barrier ] ========>
3. flag.store(true, Release); ---sync--> 4. while !flag.load(Acquire) {}
======== [ Acquire Barrier ] ========
5. Read `non_atomic_buffer` ("hello")
6. Read `metric.load(Relaxed)` (10)
Standalone Atomic Fences (std::sync::atomic::fence)
std::sync::atomic::fence(Ordering) establishes a memory ordering barrier between atomic operations without attaching the ordering to a single load or store—useful when separating a relaxed atomic decrement from a cold cleanup path (as used inside Arc::drop!):
use std::cell::UnsafeCell;
use std::sync::atomic::{AtomicBool, Ordering, fence};
use std::thread;
struct OneShotChannel<T> {
message: UnsafeCell<Option<T>>,
ready: AtomicBool,
}
// SAFETY: `ready` synchronizes the write by the sender with the read by the receiver.
unsafe impl<T: Send> Sync for OneShotChannel<T> {}
impl<T> OneShotChannel<T> {
const fn new() -> Self {
Self {
message: UnsafeCell::new(None),
ready: AtomicBool::new(false),
}
}
}
fn main() {
let channel = OneShotChannel::new();
thread::scope(|s| {
s.spawn(|| {
// SAFETY: Written before `ready` is set to `true`.
unsafe {
*channel.message.get() = Some(String::from("synchronized via fence"));
}
// A `Release` fence followed by a `Relaxed` store establishes a
// release sequence with any thread that acquires `ready == true`:
fence(Ordering::Release);
channel.ready.store(true, Ordering::Relaxed);
});
s.spawn(|| {
while !channel.ready.load(Ordering::Relaxed) {
std::hint::spin_loop();
}
// Pair with an `Acquire` fence after observing `ready == true`:
fence(Ordering::Acquire);
// SAFETY: The Release/Acquire fence pair guarantees the write to `message`
// happened-before this read!
let msg = unsafe { (*channel.message.get()).take().unwrap() };
println!("received: {:?}", msg);
});
});
}
received: "synchronized via fence"
Hardware Instructions Emitted per Ordering
Ordering |
x86_64 Instruction | AArch64 (ARM64) Instruction |
|---|---|---|
load(Relaxed) |
mov |
ldr |
load(Acquire) |
mov (TSO hardware provides acquire for free!) |
ldar (Load-Acquire) |
store(Relaxed) |
mov |
str |
store(Release) |
mov (TSO hardware provides release for free!) |
stlr (Store-Release) |
store(SeqCst) |
xchg (or mov + mfence) |
stlr (paired with ldar for SeqCst) |