multithreading · advanced
Concurrency: Memory Ordering — relaxed, acquire/release, seq_cst Explained
C++ memory model: atomic<T> operations take a memory_order argument controlling CPU/compiler reordering. relaxed: only atomicity; no happens-before; use for counters only. release (store): all prior ops ordered before this store; any load with acquire will see them. acquire (load): after this load, all stores prior to the paired release are visible. acq_rel: for read-modify-write (CAS, fetch_add, exchange) — acquire the old value, release the new. seq_cst: total global order; all threads see atomic ops in the same sequence; adds MFENCE on x86 stores. Practical rule: default to seq_cst (the default if no order specified); downgrade to acq/rel in hot paths after profiling; never use relaxed for synchronization. Architecture note: x86 has TSO (total store order) — acquire/release is free on loads; ARM/POWER are weaker and need explicit barriers. std::mutex::lock = acquire; unlock = release — gives acq/rel for free.
C++ memory ordering from weakest to strongest: relaxed (atomicity only), acquire/release (sync pair: release publishes, acquire subscribes — happens-before), acq_rel (for RMW), seq_cst (total order, default). Start with seq_cst; downgrade only after profiling.
The code
// C++ memory ordering — controls CPU/compiler reordering around atomicsatomic<int> data{0};atomic<bool> ready{false};
// Thread 1 (producer):data.store(42, memory_order_release); // all prior stores visible to acquire-loadready.store(true, memory_order_release);
// Thread 2 (consumer):while (!ready.load(memory_order_acquire)) {} // spin until ready// After acquire-load sees true → GUARANTEED to see data == 42int v = data.load(memory_order_acquire); // v == 42 ✓
// WITHOUT acquire/release — relaxed everywhere:data.store(42, memory_order_relaxed); // may not be visible before ready!ready.store(true, memory_order_relaxed);// Thread 2 sees ready==true but data might still be 0 ← BUG
// seq_cst (default for atomic<T>::load/store):data.store(42); // implicit seq_cstready.store(true); // total order: any thread sees stores in THIS order
// Summary of orderings (weakest → strongest):// relaxed: atomicity only; no ordering guarantee// release: store; all prior ops complete before this store is visible// acquire: load; after this load, all ops from the paired release are visible// acq_rel: both acquire and release (for RMW like fetch_add, CAS)// seq_cst: total global order; strongest; adds full memory fence on x86What this lesson walks through
- 01Memory model — 5 orderings from weakest to strongest
- 02relaxed — atomicity only, no ordering guarantee
- 03release + acquire — the synchronization pair
- 04acq_rel — for read-modify-write operations (CAS, fetch_add)
- 05seq_cst — total global order (the default)
- 06Practical rule — when to use which ordering
C++ has a formal memory model. The atomic<T> operations accept a memory_order parameter that controls how stores and loads may be reordered by the compiler or CPU. From weakest (fastest) to strongest (safest): relaxed → consume → acquire/release → acq_rel → seq_cst. Most concurrent data structures need acquire/release. seq_cst adds a full memory fence on every operation.
See it animated — step by step, at your own pace
Unlock the full interactive walkthrough of Concurrency: Memory Ordering — relaxed, acquire/release, seq_cst Explained and 100+ animated C++ interview lessons.