std::sync::atomic: Relaxed, Acquire, Release, SeqCst Memory Ordering
- Last updated
- Prerequisites:
- Rust ownership and shared state
- Understanding of the C++ memory model concepts
- rust
- atomics
- memory-ordering
- seqcst
- acquire-release
- concurrency
Read at your depth
The practical view
Atomic types (AtomicUsize, AtomicBool, AtomicPtr) provide lock-free operations: load, store, fetch_add, compare_exchange. The ordering parameter controls memory visibility, not atomicity (which is always guaranteed): Relaxed (no ordering constraints), Acquire (used on loads: subsequent operations can't move before it), Release (used on stores: prior operations can't move after it), AcqRel (for read-modify-write), SeqCst (strongest: a single total order). Example: atomic.load(Ordering::Acquire) after another thread's atomic.store(v, Ordering::Release) guarantees the store's prior writes are visible. Use Relaxed for counters, Acquire/Release for message passing (publish a value, then release), SeqCst when you need a global order (rare, e.g. flags that coordinate multiple threads).
The same idea in other frameworks
N/A — Rust concurrency feature.N/A — Rust feature.N/A — Rust feature.N/A — Rust feature.Legacy vs modern
Relaxed counter vs Acquire/Release handoff
A relaxed counter is fine for stats; publishing a value with correctness requires Acquire/Release to make the data visible with the flag.
READY.store(true, Ordering::Relaxed); // ❌ OLD: flag is atomic, but data may not be visibleREADY.store(true, Ordering::Release); // ✅ NEW: prior writes visible
READY.load(Ordering::Acquire); // ✅ NEW: sees everything before the ReleaseInterview gotchas
Context
This tests the fundamental model — interviewers want the hardware-instruction vs visibility distinction, because mixing them up is the most common atomics error.
The mechanical answer
Atomicity means a single operation (load, store, fetch_add, CAS) happens indivisibly — no torn reads/writes; this is guaranteed by the hardware instruction (lock-prefixed or LL/SC) regardless of the ordering parameter. Memory ordering is a SEPARATE concern: it governs how the operation's effects become visible relative to OTHER memory operations across threads. Relaxed gives atomicity but no ordering — the operation is indivisible and the value is eventually consistent, but there's no happens-before edge, so other (non-atomic) memory written before/after may not be observed in order. In safe Rust, using an Atomic (even Relaxed) cannot cause a data race on the atomic itself — the type is Send/Sync and the operation is atomic. The ordering parameter only affects visibility of surrounding memory; a Relaxed flag can still be used safely for a 'set once' signal IF no other data depends on it.
Trap
The naive answer is 'Relaxed is unsafe or buggy'. Relaxed is sound for the atomic value itself; the danger is using it to coordinate OTHER data visibility. Another trap: assuming SeqCst is always required — over-using it adds full-fence cost on weak-memory CPUs; Acquire/Release covers publish/consume patterns correctly and cheaper. Candidates who separate atomicity from ordering show real understanding.
Context
This probes the synchronizes-with relationship — interviewers want the edge semantics and the rare cases where a total order is required.
The mechanical answer
A Release store synchronizes-with an Acquire load when the load reads the value written by the store (the classic 'sees the flag' pattern). This creates a happens-before edge: everything the writing thread did BEFORE the Release store is ordered before everything the reading thread does AFTER the Acquire load — making the published data visible. SeqCst goes further: all SeqCst operations across all threads form a single total order (as if there were one global clock), which makes reasoning simpler but requires stronger barriers (StoreLoad fences on x86, full fences on ARM). SeqCst is genuinely necessary when multiple threads coordinate through multiple atomics and you need a consistent global ordering of those operations (e.g. certain lock-free protocols, mutex/futex implementations). For the common single-publish/consume pattern, Acquire/Release is correct and cheaper.
Trap
The naive answer is 'SeqCst is always the safe choice'. It's only 'safe' in the sense of being the strongest ordering; it has real cost and is rarely needed — correct Acquire/Release patterns are cheaper and equally sound. Another trap: thinking Acquire/Release works like a mutex — it orders memory, not mutual exclusion; it does not prevent multiple threads from entering a critical section. Candidates who describe the synchronizes-with edge and the total-order requirement show real depth.