I am reading the following example from https://en.cppreference.com/w/cpp/atomic/memory_order#Sequentially-consistent_ordering. And I have a hard time understanding
- under what situation
assert(z.load() != 0);
will fail. - why does using memory_order_seq_cst over memory_order_ack_rel make z never be 0.
#include <thread>
#include <atomic>
#include <cassert>
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
void write_x()
{
x.store(true, std::memory_order_seq_cst);
}
void write_y()
{
y.store(true, std::memory_order_seq_cst);
}
void read_x_then_y()
{
while (!x.load(std::memory_order_seq_cst))
;
if (y.load(std::memory_order_seq_cst)) {
++z;
}
}
void read_y_then_x()
{
while (!y.load(std::memory_order_seq_cst))
;
if (x.load(std::memory_order_seq_cst)) {
++z;
}
}
int main()
{
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x);
a.join(); b.join(); c.join(); d.join();
assert(z.load() != 0); // will never happen
}
As far as I under, for either read_x_then_y
or read_y_then_x
, they could observe a state among:
x = true
andy = false
x = false
andy = true
x = true
andy = true
x = false
andy = false
The first 2 cases make z = 1
(eventually 2), the third case makes z = 2
, and the last case makes read_x_then_y
and read_y_then_x
wait until one of `x' and 'y' become true. However, according to the cppreference
This example demonstrates a situation where sequential ordering is necessary. Any other ordering may trigger the assert because it would be possible for the threads c and d to observe changes to the atomics x and y in opposite order.
I don't understand how is that possible. How would the changes to x and y be in the opposite order?
In addition, I am wondering how would the use of memory_order_seq_cst solves the problem. Is it forcing the x.load
in read_x_then_y
must be executed before y.load
?