The gcc wiki here provides an example on memory ordering constraints.
In the below example, the wiki asserts that, if the memory ordering in use is an acquire/release, then thead-2
's assert is guaranteed to succeed while thread-3
's assert can fail.
-Thread 1- -Thread 2- -Thread 3-
y.store (20); if (x.load() == 10) { if (y.load() == 10)
x.store (10); assert (y.load() == 20) assert (x.load() == 10)
y.store (10)
}
I don't see how that is possible. My understanding is that since Thread-2
's y.store(10)
synchronizes with Thread-3
's y.load()
and since y
can be 10
if and only if x
is 10
, I say the assert
in Thread-3
should succeed. The wiki disagrees. Can someone explain why?
CodePudding user response:
and since y can be 10 if and only if x is 10,
And that's the part that is incorrect.
An acquire/release pair works between a releasing store operation and an acquiring load operation which reads the value that was release-stored.
In thread 1, we have a releasing store to x
. In thread 2, we have an acquiring load from x
. If thread 2 read the value stored by thread 1, then the two threads have an acquire/release relationship. What that means is that any values written to other objects in thread 1 before the releasing store are visible to thread 2 after its acquiring load.
In thread 2, we have a releasing store to y
. In thread 3, we have an acquiring load from y
. If thread 3 read the value stored by thread 2, then the two threads have an acquire/release relationship. What that means is that any values written to other objects in thread 2 before the releasing store are visible to thread 3 after its acquiring load.
But notice what I said: "any values written to other objects in thread 2".
x
was not written by thread 2; it was written by thread 1. And thread 3 has no relationship to thread 1.
Pure acquire/release is not transitive. If you need transitive access to memory, then you need sequential consistency. Indeed, that's what seq_cst
is for: ensuring consistently across all acquire/releases which transitively rely on each other.
Note that this is primarily about caches. A pure acquire/release operation may only flush certain caches, particularly if the compiler can clearly see exactly what memory a thread is making available in a release.