Memory Models & Atomics

compiler reordering versus CPU reordering

Your source code is a polite suggestion of the order things should happen, not a binding contract on the order they will. Two completely separate parties take liberties with that order, and to write correct concurrent code you must constrain BOTH. Constraining only one leaves the other free to break you.

The first party is the COMPILER. At compile time, an optimizer freely moves loads and stores around, keeps values in registers instead of re-reading memory, merges or deletes redundant accesses, and hoists invariant reads out of loops — all legal because, for a SINGLE thread, the visible result is unchanged (the 'as-if' rule). The second party is the CPU. At run time, even after the compiled instructions are fixed, the hardware reorders memory operations: it has store buffers that delay writes, it executes out of order, and it may let a later load complete before an earlier store drains. On x86 this CPU reordering is limited (TSO); on ARM and POWER it is much freer.

The two need different muscles to stop. A compiler barrier (e.g. asm volatile("" ::: "memory") or, properly, an atomic with an ordering tag) stops the COMPILER from reordering but emits no CPU instruction. A hardware fence (mfence, dmb, lwsync) stops the CPU but the compiler must also be told. The beauty of C11/C++ atomics is that one memory_order tag constrains BOTH at once: it tells the optimizer what it may not reorder AND makes it emit whatever CPU barrier the target needs. That is why you should reach for atomics, not hand-rolled inline-assembly barriers — and why 'it works without atomics on x86' is a trap: the compiler can still reorder even when the x86 CPU would not.

/* without atomics, the compiler may turn this... */ while (done == 0) { } /* ...into this, by hoisting the load once: */ if (done == 0) for (;;) { } /* infinite loop even if another thread later sets done=1 */

Pure compiler reordering, no CPU involved: the optimizer caches done in a register and never re-reads it.

Constraining only the CPU (a bare hardware fence) or only the compiler is a classic half-fix; use language atomics so ONE ordering tag pins both — and remember even x86, where the CPU barely reorders, gives the compiler full license to reorder.

Also called
instruction reordering重排序