Why in the following program on x86 two asserts can pass? (from the point of view of the c++/rust memory model, I understand why this is possible)
x = false
y = false
Thread1:
x = true // release
Thread2:
y = true // release
Thread3:
assert(x && !y) // acquire
Thread4:
assert(!x && y) // acquire
In theory, caches are coherent and if such an assertion assert(x && !y)
passed on a core, then the core that wrote x == true
dropped the store buffer and all cores should see the changes, including the core on which the assertion assert(!x && y)
will be executed.
12
Per [intro.races]:
Two expression evaluations conflict if one of them modifies a memory location ([intro.memory]) and the other one reads or modifies the same memory location.
and
The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, at least one of which is not atomic, and neither happens before the other, except for the special case for signal handlers described below. Any such data race results in undefined behavior.
In a nutshell, since you are not (apparently) performing any synchronization between your threads, you have a data race, which is UB.
Edit:
As Nate Eldredge pointed out, you may have intended that x
and y
were of type std::atomic_bool
and your code was more like:
std::atomic_bool x{false};
std::atomic_bool y{false};
// Thread1:
x.store(true, std::memory_order_release);
// Thread2:
y.store(true, std::memory_order_release);
// Thread3:
assert(x.load(std::memory_order_acquire) && !y.load(std::memory_order_acquire))
// Thread4:
assert(!x.load(std::memory_order_acquire) && y.load(std::memory_order_acquire))
In which case my answer changes a little bit. You still have a race condition, just no undefined behavior. Your race is between the the atomic loads and the atomic stores, which have no “ordered before” guarantees in the way the code is written; you will need some thread synchronization mechanism to guarantee your desired oredering of reads happening after writes.
7