Consider the following example of using std::memory_order_consume
to synchronize data:
struct X
{
int i;
std::string s;
};
std::atomic<X*> p;
std::atomic<int> a;
void create_x()
{
X* x=new X;
x->i=42;
x->s="hello";
a.store(99,std::memory_order_relaxed); //1
p.store(x,std::memory_order_release); //2
}
void use_x()
{
X* x;
while(!(x=p.load(std::memory_order_consume))) //3
std::this_thread::sleep(std::chrono::microseconds(1));
assert(x->i==42); //4
assert(x->s=="hello"); // 5
assert(a.load(std::memory_order_relaxed)==99); // 6
}
int main()
{
std::thread t1(create_x);
std::thread t2(use_x);
t1.join();
t2.join();
}
Anthony Williams in his book "Concurrency in Action" gives the following explanation:
Even though the store to a 1 is sequenced before the store to p 2, and the store to p is tagged memory_order_release, the load of p 3 is tagged memory_order_consume. This means that the store to p only happens before those expressions that are dependent on the value loaded from p. This means that the asserts on the data members of the X structure (4 and 5) are guaranteed not to fire, because the load of p carries a dependency to those expressions through the variable x. On the other hand, the assert on the value of a 6 may or may not fire; this operation isn’t dependent on the value loaded from p, and so there’s no guarantee on the value that’s read. This is particularly apparent because it’s tagged with memory_order_relaxed.
This explanation makes sense, but when I try to think about it in terms of happens-before relationships as defined by cppreference, I am failing to conclude the same:
The last statement seems to contradict what is written in the book. Is there a mistake in my logical chain or the fact that the store 1 happens-before the load 6 does not necessarily mean that the effects of 1 are visible to 6?
Your logical error is in the last step. The definition of happens-before in C++23 (N4950 final draft) is:
An evaluation A happens before an evaluation B (or, equivalently, B happens after A) if:
— A is sequenced before B, or
— A inter-thread happens before B.
Notice there is not a third clause saying "if A happens before X and X happens before B". The happens-before relation is not defined as transitive.
So it's true that 1 happens before 3 and 3 happens before 6, but it does not follow that 1 happens before 6.
Notice too that you cannot show that 1 inter-thread happens before 6.
(When there are no consume operations, so that no operation is dependency-ordered before any other, then you can show that happens-before is transitive, and is the transitive closure of the union of sequenced-before and synchronizes-with. This matches how simply-happens-before is defined, and there is a note pointing this out.)