Synchronization Primitives
Ordering
std::sync::atomic::Ordering is a #[non_exhaustive] enum passed to every atomic method (load, store, swap, fetch_add, compare_exchange) that specifies how the compiler and CPU hardware may reorder surrounding memory accesses relative to that atomic operation.
The Five Variants of std::sync::atomic::Ordering
#[non_exhaustive]
pub enum Ordering {
Relaxed,
Release,
Acquire,
AcqRel,
SeqCst,
}
Ordering Variant |
Valid On | Synchronization & Memory-Ordering Guarantee |
|---|---|---|
Ordering::Relaxed |
All operations (load, store, RMW) |
Guarantees atomicity of this single variable only; imposes zero ordering constraints on other memory reads/writes |
Ordering::Release |
Writes (store, RMW) |
Prevents any preceding read or write on the current thread from being reordered after this store |
Ordering::Acquire |
Reads (load, RMW) |
Prevents any subsequent read or write on the current thread from being reordered before this load; observes all writes performed before a matching Release store |
Ordering::AcqRel |
Read-Modify-Write (swap, fetch_*, compare_exchange) |
Performs an Acquire on the read half and a Release on the write half of a read-modify-write operation |
Ordering::SeqCst |
All operations (load, store, RMW) |
Acquire/Release/AcqRel plus establishes a single global total order of all SeqCst operations across all threads |
Strength Hierarchy of `std::sync::atomic::Ordering`:
[ Ordering::SeqCst ] (Strongest: Total global order + Acq/Rel)
^
|
[ Ordering::AcqRel ] (Both Acquire and Release on RMW ops)
^ ^
/ \
[ Ordering::Acquire ] [ Ordering::Release ]
(On loads / RMW) (On stores / RMW)
\ /
v v
[ Ordering::Relaxed ] (Weakest: Single-variable atomicity only)
Valid Ordering Variants per Atomic Method
Passing Ordering::Release or Ordering::AcqRel to .load()—or passing Ordering::Acquire or Ordering::AcqRel to .store()—panics at runtime!
| Atomic Operation | Allowed Ordering Arguments |
Forbidden (Panics at Runtime!) |
|---|---|---|
.load(order) |
Relaxed, Acquire, SeqCst |
Release, AcqRel |
.store(val, order) |
Relaxed, Release, SeqCst |
Acquire, AcqRel |
.swap(val, order) / .fetch_*(val, order) |
Relaxed, Acquire, Release, AcqRel, SeqCst |
(All 5 allowed) |
.compare_exchange(cur, new, success, failure) |
success: any of the 5; failure: Relaxed, Acquire, SeqCst |
failure cannot be Release or AcqRel |
Practical Example: Relaxed Metrics vs. Release/Acquire Flag Signaling
- Use
Ordering::Relaxedwhen incrementing standalone metrics or generating unique IDs where no other memory depends on the counter value. - Pair
Ordering::Release(on the writer) withOrdering::Acquire(on the reader) when an atomic variable signals that other memory has been initialized or updated:
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::thread;
static DATA_SLOT: AtomicU64 = AtomicU64::new(0);
static IS_PUBLISHED: AtomicBool = AtomicBool::new(false);
fn main() {
thread::scope(|s| {
// Producer thread:
s.spawn(|| {
// 1. Write the payload (can be Relaxed or non-atomic inside UnsafeCell):
DATA_SLOT.store(0xDEAD_BEEF, Ordering::Relaxed);
// 2. Publish with `Ordering::Release` so step 1 is guaranteed to
// happen-before any reader that observes `IS_PUBLISHED == true`!
IS_PUBLISHED.store(true, Ordering::Release);
});
// Consumer thread:
s.spawn(|| {
// 3. Spin with `Ordering::Acquire` until `IS_PUBLISHED` becomes true:
while !IS_PUBLISHED.load(Ordering::Acquire) {
std::hint::spin_loop();
}
// 4. Guaranteed by Release/Acquire synchronization to observe `0xDEAD_BEEF`!
let payload = DATA_SLOT.load(Ordering::Relaxed);
println!("consumer observed payload: {:#x}", payload);
});
});
}
consumer observed payload: 0xdeadbeef