[Quote from Atomics in Rust, chapter 3]
A happens-before relationship is created between a release fence and an acquire fence if any store after the release fence is observed by any load before the acquire fence.
For example, suppose we have one thread executing a release fence followed by three atomic store operations to different variables, and another thread executing three load operations from those same variables followed by an acquire fence, as follows:
In this situation, if any of the load operations on thread 2 loads the value from the corresponding store operation of thread 1, the acquire fence on thread 2 happens-before the release fence on thread 1.
Could somebody please explain in simple words what it means?
Where is this from? I'm not convinced that the claim is true.
The example is a bit incomplete, as the happens-before relationship, if it happens, is pretty useless: a happens-before relation ship between the
fence(Release) in Thread 1 and
fence(Acquire) in Thread 2 would guarantee that the effects of all writes before the
fence(Release) on Thread 1 will be visible in Thread 2 after the
fence(Acquire). Since there’s nothing before
fence(Release) and neither is there anything after
fence(Acquire) in this example, the consequences of this happens-before relationship are actually none.
This is a quote from third chapter of: "Rust Atomics and Locks".
With the above explanation out of the way, let’s do a different example…
The intuitive effect of the fence is that if
fence(Release); has already happened from the perspective of Thread 2, then Thread 2 will be guaranteed to see all memory effects before this fence, i.e. the stores to
B are visible to the loads in Thread 2 after the
The difficulty is in formalizing what it means that “
fence(Release); has already happened from the perspective of Thread 2”; importantly, it could always be the case that - intuitively speaking - Thread 2 reaches
fence(Acquire) before Thread 1 reaches
fence(Release), in which case you’d expect no guarantees whatsoever about any effects of
B.store being visible.
fence instruction doesn’t really “do” anything, so what does “
fence(Release); has already happened from the perspective of Thread 2” mean now!? From the perspective of Thread 2 the
fence is completely invisble (as I just mentioned, it doesn’t “do” anything), so how can Thread 2 ever “know” it did already “happen”? The way this is nailed down in this, slightly cumbersome, definition you quote is that other memory effects in the code around the
fences are used as an indicator. The
fence itself cannot be seen from
Thread 2, but modifications of memory that (within Thread 1) happen (logically) after the
fence(Release) might be visible. And it is such memory effects that are used as an indicator: if anything that happens after the
fence(Release) in Thread 1 is visible to Thread 2 by the time (i.e. before)
fence(Acquire) is reached, then we do consider Thread 2 to “know” - so to speak - that
fence(Release) has happened, and we do expect all the guarantees about
B.store effects being visible (in Thread 2) after the
fence(Acquire) as well.
To recap, in the above example, this means that
If Thread 2 sees the effect of either (and possible, but not necessarily both)
D.store (in the corresponding
then Thread 2 is also guaranteed to see both the effects of
B.store (in the corresponding
Basically, the idea is that
A.store(1, Relaxed) happens-before
fence(Acquire), therefore we can conclude that
That said, I think there's a problem with the argument because it makes the following step:
- We know that
A.store(1, Relaxed) comes before
A.load(Relaxed) in the modification order for
A.store(1, Relaxed) happens-before
However, I don't see any rules in the C++ documentation for memory orders that would allow you to make this conclusion. That document makes statements of the form "if stuff then A before B in modification order", but it never makes any statements of the form "if A before B in modification order then stuff".
Thanks, that really clarified for me. So basically what in between those fences is guaranteed to happen in the order of the instructions specified.
Thanks once again.
Aha! I found the rule that makes the statement correct.
A release fence FA in thread A synchronizes-with an acquire fence FB in thread B, if
- There exists an atomic object M,
- There exists an atomic write X (with any memory order) that modifies M in thread A
- FA is sequenced-before X in thread A
- There exists an atomic read Y (with any memory order) in thread B
- Y reads the value written by X (or the value would be written by release sequence headed by X if X were a release operation)
- Y is sequenced-before FB in thread B
In this case, all non-atomic and relaxed atomic stores that are sequenced-before FA in thread A will happen-before all non-atomic and relaxed atomic loads from the same locations made in thread B after FB.
So this is a special property of fences that do not apply to normal acquire/release operations.
Hey! Where did you find that rule?
There's a link to the source at the bottom of the quote block.