Visual walkthrough — Memory model — happens-before, acquire-release semantics
Step 1 — Two threads, one shared box of memory
WHAT. Imagine your program's memory as a long shelf of numbered boxes. Each box holds one value. A thread is just a worker who walks along the shelf, reading and writing boxes, one instruction at a time. We have two workers, T1 and T2, and they share the same shelf.
WHY. Before we can talk about ordering writes, we need the picture of who writes and who reads. Everything else is layered on this.
PICTURE. In the figure, box data starts at 0 and box ready starts at false. T1 will eventually put a real value into data and then flip ready. T2 keeps re-reading ready, and only when it sees true does it read data.

Step 2 — Program order is NOT the order the world sees
WHAT. T1's source code says: line (1) data = 42; then line (2) ready = true;. That top-to-bottom order in the code is called program order. But the value in data and the value in ready do not necessarily become visible to T2 in that same order.
WHY. Three separate machines are allowed to shuffle the timing of when a write is seen:
- the compiler (as-if rule) may emit the stores in a different order,
- the CPU runs out of order and holds writes in a store buffer,
- that store buffer drains to other cores whenever it likes.
Each of these is invisible inside one thread — T1 always reads back its own writes correctly. The shuffle only shows across threads.
PICTURE. The figure shows T1's tidy program order on the left, and the messy order T2 might actually observe on the right: ready can flip to true before data's 42 has arrived.

Step 3 — The bug, drawn: T2 opens an empty box
WHAT. Suppose T2's loop reads ready == true and then reads data. Because Step 2 lets ready become visible first, T2 can read data while it is still 0 — the old value. The assert(data == 42) fires.
WHY. There is no rule connecting T1's write of data to T2's read of data. When two threads touch the same plain box, one writes, and nothing orders them, that is by definition a problem.
PICTURE. Red X: T2 sees the flag up but the box still holds 0. The dashed arrow (the connection we wish existed) is missing.

Step 4 — Introduce the atomic box (still not enough)
WHAT. We upgrade ready from a plain bool into a std::atomic<bool>. An atomic box guarantees its own reads and writes are indivisible: T2 never sees a half-written ready.
WHY. We need a special box because ordinary boxes give no cross-thread promises at all. The atomic is the hook onto which we will later attach ordering. But note carefully: atomicity alone still does not order data. If we tag the operations relaxed, we only promise "no torn read of ready" — nothing about data.
PICTURE. The ready box now wears an "atomic" badge (no torn reads), but the dashed wish-arrow to data is still missing under relaxed.

Term by term: store writes the box; the value true is what lands in it; relaxed is the promise level — here, no promise about data.
Step 5 — The release tag: a one-way floor under the store
WHAT. Change T1's store to std::memory_order_release. This adds a rule to the store: no memory operation that appears before it in program order may be moved to after it. The write data = 42 is now pinned below nothing and above the release — it cannot slip past.
WHY. We want T1 to finish writing data before the flag can possibly become visible. release is exactly the tool that says "everything I did earlier stays earlier." It is a downward barrier (a floor): earlier work cannot fall through it.
PICTURE. A solid orange floor sits under the ready.store. The data = 42 arrow bounces off it — it can never go below. This is the "publish the box, then raise the flag" order, now enforced.

Step 6 — The acquire tag: a one-way ceiling over the load
WHAT. Change T2's load to std::memory_order_acquire. This adds a rule to the load: no memory operation that appears after it in program order may be moved to before it. So T2's read of data is pinned below the acquire-load — it cannot happen until after T2 has read ready.
WHY. We want T2 to read the flag first, and only then read data. acquire says "once I've seen the signal, everything I do afterwards stays afterwards." It is an upward barrier (a ceiling): later work cannot rise above it.
PICTURE. A solid magenta ceiling sits over the ready.load. T2's read data arrow bounces off it from below — it can never rise above the load.

Step 7 — The handshake: floor + ceiling snap together
WHAT. The magic happens only when T2's acquire-load reads the exact value T1's release-store wrote (true). At that instant a cross-thread link forms — the release synchronizes-with the acquire.
WHY. Synchronization is not about clock time; it is about data flow. The value true had to travel from T1's box into T2's register. That journey is the handshake. If T2 read some other value (an older false), no journey happened, so no link forms. The parent note called this "must read the stored value".
PICTURE. The orange floor of the release and the magenta ceiling of the acquire click together into one continuous rail. The bounced-off data = 42 (below the floor) and read data (above the ceiling) are now on opposite ends of one rigid connection.

Step 8 — Chaining into happens-before (the guarantee)
WHAT. We now assemble three links into one guarantee. Label the four events:
- (1)
data = 42 - (2)
ready.store(true, release) - (3)
ready.load(acquire) == true - (4)
read data
The chain:
Reading the symbols: is sequenced-before (order inside one thread); is synchronizes-with (the cross-thread handshake).
WHY. Happens-before is defined as the transitive closure: if you can walk from A to B along sb and sw arrows, then A happens-before B. Following the arrows: , so
Therefore T2's read data is guaranteed to see 42. And because (1) and (4) are now ordered, they are no longer a data race — the UB is gone.
PICTURE. Four nodes, three colored arrows spelling the chain; a big green check on the final assert(data == 42).

Step 9 — Edge & degenerate cases (so no scenario surprises you)
WHAT / WHY, case by case:
- Case A — relaxed store or load. No floor or ceiling, no synchronizes-with. The chain in Step 8 breaks at the
swlink.(1)does not happen-before(4): the assert may fail. (This is Step 4's picture, still broken.) - Case B — acquire reads the old
false. The loop condition!readyis still true, so T2 keeps looping; it has not yet read the stored value, so no handshake yet — exactly as the definition demands. The guarantee only arrives on the iteration that readstrue. - Case C — the store never happens (T1 crashes before (2)). T2 spins forever reading
false. No false guarantee is ever made — you simply never enter theassert. Safe, if stuck. - Case D — two independent atomic flags, two readers (IRIW). Acquire-release orders each thread along its own chain but gives no single global order. Two readers can disagree on the order of two independent stores. To force one global order you need
seq_cst— the strongest tag (or a fence).
PICTURE. A 2×2 grid: A (broken chain), B (still looping), C (spin forever), D (two readers disagree). Each cell shows why the guarantee holds or does not.

The one-picture summary
Everything above compressed: T1 writes data, the release floor pins that write below the flip; the value true travels across (the handshake); the acquire ceiling pins T2's read of data above the load; the transitive chain makes (1) happen-before (4), and the box is guaranteed full.

Recall Feynman: retell the whole walkthrough in plain words
Ravi and Maya share a shelf of boxes. Ravi writes a secret into the data box, then raises a flag in the ready box. Normally the world is allowed to shuffle when Maya sees these two events, so she might see the flag up while the secret box is still empty — that's the bug (a data race, undefined behavior).
To fix it, Ravi raises the flag with a release: a rule that says "my earlier writing can't slip below this flag-raise" — a floor. Maya reads the flag with an acquire: "once I've seen the flag, my later reading can't slip above it" — a ceiling. The floor and ceiling only snap together if Maya reads the exact flag Ravi raised — that's the handshake, and it's about the value travelling from him to her, not about the clock.
Once they snap, Ravi's writing → his flag → her seeing the flag → her reading form one unbroken chain (happens-before), so Maya is guaranteed the secret box holds 42. If either of them used relaxed, there's no floor/ceiling, no handshake, and Maya can open an empty box. And this handshake orders things only along their own chain — a third friend watching two different flags might see events in a different order, which is why global agreement needs the stronger seq_cst.