Consider the following code:
#include <atomic>
#include <thread>
#include <cassert>
#include <memory>
int i = 0;
std::atomic_int a{0};
int main()
{
std::thread thr1{[]
{
i = 1; // A
a.store(1, std::memory_order::release); // B
}};
std::thread thr2{[]
{
while (a.load(std::memory_order::relaxed) != 1); // C
a.store(2, std::memory_order::release); // D
}};
std::thread thr3{[]
{
while (a.load(std::memory_order::acquire) != 2); // E
assert(i == 1); // F
}};
thr1.join();
thr2.join();
thr3.join();
}
My assumption is that the assert may or may not fail and the behavior here is undefined.
Although we have the happens-before relationships such as A->B, C->D, D->E, E->F, we don't have such a relationship for B->C because of the relaxed load in C.
On the other hand, https://en.cppreference.com/w/cpp/atomic/memory_order says that
All memory writes (including non-atomic and relaxed atomic) that happened-before the atomic store from the point of view of thread A, become visible side-effects in thread B. That is, once the atomic load is completed, thread B is guaranteed to see everything thread A wrote to memory. This promise only holds if B actually returns the value that A stored, or a value from later in the release sequence.
But we can't say that B->D is a release sequence headed by B because there are no read-modify-write operations on a
whatsoever, so this paragraph doesn't work here.
Am I right in my understanding?
while
loops, the variablei
can only be tested after the code in threads 1 and 2 have executed. The assert can never fail. That is even independent of the memory orders used. – Whitbyi = 1
will be visible there? – Calque