Say, I create an object of type Foo
in thread #1 and want to be able to access it in thread #3.
I can try something like:
std::atomic<int> sync{10};
Foo *fp;
// thread 1: modifies sync: 10 -> 11
fp = new Foo;
sync.store(11, std::memory_order_release);
// thread 2a: modifies sync: 11 -> 12
while (sync.load(std::memory_order_relaxed) != 11);
sync.store(12, std::memory_order_relaxed);
// thread 3
while (sync.load(std::memory_order_acquire) != 12);
fp->do_something();
- The store/release in thread #1 orders
Foo
with the update to 11 - thread #2a non-atomically increments the value of
sync
to 12 - the synchronizes-with relationship between thread #1 and #3 is only established when #3 loads 11
The scenario is broken because thread #3 spins until it loads 12, which may arrive out of order (wrt 11) and Foo
is not ordered with 12 (due to the relaxed operations in thread #2a).
This is somewhat counter-intuitive since the modification order of sync
is 10 → 11 → 12
The standard says (§ 1.10.1-6):
an atomic store-release synchronizes with a load-acquire that takes its value from the store (29.3). [ Note: Except in the specified cases, reading a later value does not necessarily ensure visibility as described below. Such a requirement would sometimes interfere with efficient implementation. —end note ]
It also says in (§ 1.10.1-5):
A release sequence headed by a release operation A on an atomic object M is a maximal contiguous subsequence of side effects in the modification order of M, where the first operation is A, and every subsequent operation
- is performed by the same thread that performed A, or
- is an atomic read-modify-write operation.
Now, thread #2a is modified to use an atomic read-modify-write operation:
// thread 2b: modifies sync: 11 -> 12
int val;
while ((val = 11) && !sync.compare_exchange_weak(val, 12, std::memory_order_relaxed));
If this release sequence is correct, Foo
is synchronized with thread #3 when it loads either 11 or 12.
My questions about the use of an atomic read-modify-write are:
- Does the scenario with thread #2b constitute a correct release sequence ?
And if so:
- What are the specific properties of a read-modify-write operation that ensure this scenario is correct ?
store(11)
andcompare_exchange(11, 12)
constitute a release sequence? They satisfy all the requirements in the paragraph you quoted. – AntonFoo
only becomes (reliably) visible when (and if) thread #3 loads 11. If it loads 12, it has become impossible to accessFoo
because it is is unordered wrt 12, and 11 is 'lost' (I referred to that scenario in the question as 'broken') – LWimseyFoo
is ready. A separate store doesn't have this property in C++11. – Peter Cordes