Both Read Zero

Two threads run once each on an x86-64 machine, with mo the same ordering everywhere:

std::atomic<int> X{0}, Y{0};
int r1, r2;

// thread 1                 // thread 2
X.store(1, mo);             Y.store(1, mo);
r1 = Y.load(mo);            r2 = X.load(mo);

With mo = std::memory_order_relaxed, a harness that repeats this in a loop observes r1 == 0 && r2 == 0 roughly once every 6,600 trials. Changing mo to std::memory_order_seq_cst makes that outcome disappear entirely. What did seq_cst change on this machine?

A

It attached an acquire barrier to each load, and an acquire barrier is what blocks StoreLoad reordering.

B

It forced the store to drain before the load, which on x86 means the compiler emits a locked instruction or mfence between them. That is the only barrier that blocks StoreLoad reordering, and StoreLoad is the one reordering x86 permits.

C

Nothing at the hardware level. x86 already forbids all reordering, so seq_cst only stopped the compiler from swapping the two statements.

D

It made the loads and stores atomic. Relaxed accesses are not atomic, so r1 could have been read while the store to Y was half complete.