Why isn't a C++11 acquire_release fence enough for Dekker synchronization?

atomic, c++11, memory-fences, multithreading, synchronization

Solution

As I understand it (mainly from reading Jeff Preshings blog), an `atomic_thread_fence(std::memory_order_acq_rel)` prevents any reorderings except for `StoreLoad`, i.e., it still allows to reorder a `Store` with a subsequent `Load`. However, this is exactly the reordering that has to be prevented in your example.

More precisely, an `atomic_thread_fence(std::memory_order_acquire)` prevents the reordering of any previous `Load` with any subsequent `Store` and any subsequent `Load`, i.e., it prevents `LoadLoad` and `LoadStore` reorderings across the fence.

An `atomic_thread_fence(std::memory_order_release)` prevents the reordering of any subsequent `Store` with any preceding `Store` and any preceding `Load`, i.e., it prevents `LoadStore` and `StoreStore` reorderings across the fence.

An `atomic_thread_fence(std::memory_order_acq_rel)` then prevents the union, i.e., it prevents `LoadLoad`, `LoadStore`, and `StoreStore`, which means that only `StoreLoad` may still happen.

Problem

The failure of Dekker-style synchronization is typically explained with reordering of instructions. I.e., if we write ``` atomic_int X; atomic_int Y; int r1, r2; static void t1() { X.store(1, std::memory_order_relaxed) r1 = Y.load(std::memory_order_relaxed); } static void t2() { Y.store(1, std::memory_order_relaxed) r2 = X.load(std::memory_order_relaxed); } ``` Then the loads can be reordered with the stores, leading to `r1==r2==0`. I was expecting an acquire_release fence to prevent this kind of reordering: ``` static void t1() { X.store(1, std::memory_order_relaxed); atomic_thread_fence(std::memory_order_acq_rel); r1 = Y.load(std::memory_order_relaxed); } static void t2() { Y.store(1, std::memory_order_relaxed); atomic_thread_fence(std::memory_order_acq_rel); r2 = X.load(std::memory_order_relaxed); } ``` The load cannot be moved above the fence and the store cannot be moved below the fence, and so the bad result should be prevented. However, experiments show `r1==r2==0` can still occur. Is there a reordering-based explanation for this? Where's the flaw in my reasoning?

Original source