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 = trueandy = falsex = falseandy = truex = trueandy = truex = falseandy = 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?
For
read_x_then_y:The first loop waits until
xis loaded astrue. Then++zis executed ifyis loaded astrue. At this point,ycould be eithertrueorfalse. Both executions are possible.Likewise, for
read_y_then_x, both executions are possible.What prevents the following?
read_x_then_yloadsxastruebutyasfalseread_y_then_xloadsyastruebutxasfalseIn this case, the assert would trigger.
The answer is that this is the guarantee sequentially consistent ordering provides. All such atomic operations share a single total modification order. Either the store to
xhappens first, or the store toyhappens first, in this order. All loads will agree on that order.Release stores and acquire loads provide much less: any stores before the release stores in the same thread are visible to acquire loads in other threads. So all variables stored to by
write_xbefore the store, would be loadable byread_x_then_yafterxis loaded. Of course, there are no such stores.