This snip is from Herb Sutter's Atomic Weapons talk slide from page number 19.

If I am understanding this correctly, what Herb is saying is that, for the assert() in thread 3 to succeed, this has to follow sequential consistency. So the following code will not fail the assertion.
int g{0}; // normal int
std::atomic<int> x{0}, y{0}; // atomics
void thread1() {
g = 1;
x.store(1, std::memory_order_seq_cst);
}
void thread2() {
if(x.load(std::memory_order_seq_cst) == 1)
y.store(1, std::memory_order_seq_cst);
}
void thread3() {
if(y.load(std::memory_order_seq_cst) == 1)
assert( g == 1 );
}
But wouldn't this also work if release/acquire was used instead as follows?
int g{0}; // normal int
std::atomic<int> x{0}, y{0}; // atomics
void thread1() {
g = 1; // A
x.store(1, std::memory_order_release);
}
void thread2() {
if(x.load(std::memory_order_acquire) == 1)
y.store(1, std::memory_order_release);
}
void thread3() {
if(y.load(std::memory_order_acquire) == 1)
assert( g == 1 ); // B
}
Q1 - Doesn't //A simply-happens-before //B ensure that the assertion is valid, since no other thread writes to g?
Q2 - Am I misunderstanding the purport of the slide, or is something wrong on the slide?
Yes, acquire/release ordering would be sufficient here.
With acquire/release ordering and g being non-atomic, the code is okay because
g = 1 is sequenced before T1:x = 1x = 1 synchronizes with T2:x == 1x == 1 is sequenced before T2:y = 1y = 1 is synchronizes with T3:y == 1y = 1 is sequenced before T3:g == 1You have a happens before relationship running all the way from T1:g = 1 to T3:g == 1, and this means that there is no data race and that T3 has to read 1 (see [intro.races] p13).
Obviously, Thread 3 could also "run first", synchronization doesn't happen, and this whole happens before chain doesn't exist.
However, if T3:y == 1 is true, the synchronizations must have taken place, T1:g = 1 happens before T3:assert(g == 1), and the assertion cannot fail.
In the talk, Herb mentions that g is non-atomic.
That makes it really hard to tell why he thinks that sequential consistency is required, considering that sequentially consistent operations on x don't provide any relevant extra guarantees regarding g.
Perhaps if g was atomic, one might suspect that g == 1 wouldn't always be true because of [intro.races] p13:
The value of an atomic object M, as determined by evaluation B, is the value stored by some unspecified side effect A that modifies M, where B does not happen before A.
In other words, assert(g == 1) could read arbitrarily far into the past modifications of g (but not into the future).
This is the rule that misled Herb into thinking that this example requires sequential consistency.
However, a happens before chain as described above also imposes requirements on atomics:
If a side effect X on an atomic object M happens before a value computation B of M, then the evaluation B takes its value from X or from a side effect Y that follows X in the modification order of M.
In our case, g = 1 happens before assert(g == 1), so the assertion has to pass (or take a value of g that is written after g = 1, but no such write exists in this sample code).
Note: a happens before chain is upheld because an alternating pattern of sequenced before and synchronizes with is covered by inter-thread happens before.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With