a memory barrier
If memory ordering is the freedom the CPU and compiler have to shuffle and delay your reads and writes, a memory barrier is the command that says 'stop reordering here'. Picture a line painted across a busy workshop floor: workers may rearrange tasks however they like on each side of the line, but nothing is allowed to cross it. A memory barrier (also called a fence) is exactly such a line in the instruction stream: it forces certain memory operations before the barrier to complete and become visible before any memory operations after the barrier are allowed to start.
There are a few flavors, but the idea is always to restore a guarantee that reordering would otherwise take away. A store barrier ensures earlier writes are flushed out (made visible) before later writes. A load barrier ensures earlier reads complete before later reads. A full barrier orders everything across it. The most useful pattern is the acquire/release pair: a release barrier on the writer side guarantees that everything it wrote before publishing a flag is visible to anyone who later sees that flag; an acquire barrier on the reader side guarantees that once it sees the flag, it also sees all those earlier writes. That pair is exactly what makes a 'set data, then set ready' / 'wait for ready, then read data' handshake safe.
In practice you rarely write barriers by hand. Mutex acquire and release, semaphore wait and signal, and the atomic operations in a good library already contain the right barriers, which is why ordinary lock-based code just works. You reach for explicit barriers only in low-level lock-free programming, and it is treacherous: a missing barrier produces a bug that appears only on certain processors under certain timings and may never show up in testing. Barriers also have a cost — they prevent optimizations and can stall the pipeline — so they are used sparingly and precisely, not sprinkled around for safety.
Fixing the earlier handshake: the producer writes data=42, then issues a release barrier, then sets ready=true. The consumer reads ready, issues an acquire barrier, then reads data. Now if it sees ready true, it is guaranteed to see data as 42 — never the stale 0.
A release barrier on the writer + an acquire barrier on the reader makes the handoff safe.
A missing barrier is among the worst bugs in computing: correct on your machine, wrong on another, invisible in testing. Prefer library locks/atomics, which embed barriers, over rolling your own.