Synchronization

the memory-ordering and visibility problem

You might assume that when one thread writes a value to memory, another thread will immediately see it, and that writes happen in the order you wrote them. On modern multi-core hardware, neither assumption is safe. To go fast, the compiler may reorder independent memory operations, and each CPU core has its own caches and write buffers, so a write by one core can be delayed before it becomes visible to another core — and writes can become visible in a different order than they were issued. This is the memory-ordering and visibility problem.

A concrete trap: thread A does data = 42; then ready = 1;. Thread B spins until ready == 1, then reads data, expecting 42. Without synchronization, thread B can observe ready == 1 while still seeing the old value of data — because the two writes by A became visible to B out of order, or B's view of data is stale in its cache. Nothing is 'wrong' with the hardware; it is doing exactly what the memory model permits. The fix is a memory ordering constraint, often via a memory barrier (fence) that forbids certain reorderings, or via atomic operations with a specified ordering (acquire/release semantics). This is the deep reason a lock does more than mutual exclusion: unlocking performs a release that publishes all the writes you made inside the critical section, and locking performs an acquire that makes those writes visible to the next thread. A lock both excludes other threads AND publishes your writes.

This matters because it explains why you cannot just 'use a plain flag' to hand data between threads, and why correct synchronization is about visibility as much as exclusion. The good news for most programmers: if you protect all shared data with a mutex (or use atomics with their default sequentially-consistent ordering), the library and hardware insert the right barriers for you, and you never have to reason about reordering directly. The danger zone is hand-rolled lock-free code and clever flag tricks, where you must reason explicitly about which writes are published to whom and when — which is exactly why this entry only names the problem and Volume II treats memory models in full.

Thread A: data = 42; ready = 1; Thread B: while (!ready) {} use(data);. With plain ints, B may see ready == 1 yet read a stale data. A lock around both, or atomics with acquire/release, guarantees that seeing ready == 1 also means seeing data == 42.

A lock not only excludes other threads, it publishes your writes; that is why a plain flag is unsafe.

Reordering and stale views are permitted by the hardware and compiler memory model; they are not bugs in the chip. If you guard all shared data with a mutex or use sequentially-consistent atomics, the barriers are handled for you — only hand-rolled lock-free code forces you to reason about ordering directly. And volatile in C is NOT a thread-synchronization tool; it does not provide the needed barriers.

Also called
memory orderingvisibilitymemory modelmemory barrier記憶體順序可見性