🧭Go deeper — read the bookMemory ordering & atomics, deep dive— runnable code & full walkthrough →

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.

🔑 Key line

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 atomics
atomic<int> data{0};
atomic<bool> ready{false};
// Thread 1 (producer):
data.store(42, memory_order_release); // all prior stores visible to acquire-load
ready.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 == 42
int 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_cst
ready.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 x86

What this lesson walks through

  1. 01Memory model — 5 orderings from weakest to strongest
  2. 02relaxed — atomicity only, no ordering guarantee
  3. 03release + acquire — the synchronization pair
  4. 04acq_rel — for read-modify-write operations (CAS, fetch_add)
  5. 05seq_cst — total global order (the default)
  6. 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.

← Previous
Atomics & Memory Ordering (acquire/release)
Next →
Concurrency: Lock-Free Queue — Michael-Scott CAS Loop, ABA, Memory Hazards