Consider the example below. Assume that barrier
is initialized to 0.
There is one producer thread and two consumer threads that constantly check barrier
. If the barrier is set, they decrease runcnt
. The producer thread waits for runcnt
to reach 0. I am confused about the order of multiple store operations inside the producer.
If the order is like it is written, I think the code would run as expected. But if the barrier
store is reordered before runcnt
store, it seems the assert check would fail.Am I missing anything? Is there a way to fix this?
extern atomic<int> barrier[2];atomic_int runcnt{0};void producer() { runcnt.store(2, memory_order_relaxed); barrier[0].store(1, memory_order_relaxed); barrier[1].store(1, memory_order_relaxed); while (runcnt.load(memory_order_relaxed)) { cpu_pause(); }}void consumer(unsigned index) { while (true) { if (barrier[index].exchange(false, memory_order_relaxed)) { int prev = runcnt.fetch_sub(1, memory_order_relaxed); assert(prev > 0); } }}
Update
As @peter-cordes pointed out, unlike regular release stores that are uni-directional, std::atomic_thread_fence(release)
serves as bidirectional barrier between stores (loads can still be reordered).
Therefore, this is the fixed code:
void producer() { runcnt.store(2, memory_order_relaxed); std::atomic_thread_fence(release); // <- Prevents reordering of barrier stores before runcnt.store. barrier[0].store(1, memory_order_relaxed); barrier[1].store(1, memory_order_relaxed); while (runcnt.load(memory_order_relaxed)) { cpu_pause(); }}