In this comment under a CWG issue, Jens Maurer says
The read-compare-write is a single, indivisible operation ("atomically").
However, as discussed in
Even though we haven't had any 100% confirmed answers, We seem to consider the read-modify-write operation to comprise two operations: read and write operations, which is also implied by this formal wording [atomics.order] p10
Atomic read-modify-write operations shall always read the last value (in the modification order) written before the write associated with the read-modify-write operation.
The wording mentions the "read" and "write" associated with a read-modify-write operation. However, Jens thinks a read-modify-write operation is a single indivisible operation.
What is the intent meaning in the standard here?
Update
A relevant example is:
std::atomic<int> x{0};
// Thread 1:
int expected = 0;
x.compare_exchange_strong(expected,1,std::memory_order::release,std::memory_order::relaxed); // #1
// Thread 2:
int expected = 1;
while(
/*#2*/
x.compare_exchange_strong(expected,2,std::memory_order::acquire,std::memory_order::relaxed)
){}
Given the assumption that the RMW operation in thread 2 reads the value written by the RMW operation in thread 1(i.e. #2 only runs one iteration)
[atomics.order] p2 says:
An atomic operation A that performs a release operation on an atomic object M synchronizes with an atomic operation B that performs an acquire operation on M and takes its value from any side effect in the release sequence headed by A.
[atomics.order] p1 says:
memory_order::release, memory_order::acq_rel, and memory_order::seq_cst: a store operation performs a release operation on the affected memory location.
memory_order::acquire, memory_order::acq_rel, and memory_order::seq_cst: a load operation performs an acquire operation on the affected memory location.
That means, if A
is a store operation and B
is a load operation, and they satisfy [atomics.order] p2, then they synchronize.
IIUC #1
synchronizes with #2
, however, #1
and #2
are both RMW operations, Is #1
a store operation that performs a release operation, and #2
is a load operation that performs an acquire operation?
acq_rel
orseq_cst
RMW reordering with a later load (as Nate's answer on your first link shows), but the load side of the RMW has acquire semantics so isn't allowed to reorder that way. So we can't explain the observed behaviour in terms of an atomic RMW being one pair of operations that stay glued together in the total order of events. – WonderfulRMW
s can never read the value written by the same modification, right? – Sandsrelaxed
everywhere (I'm ignoring the fence, since it can't retroactively affect whether we enter theif
or not). – Tersanctusme.exchange(true, std::memory_order_seq_cst);
so both the load and store sides have seq_cst semantics (which is why it compiles toldaxrb
/stlxrb
in the retry-loop, note thea
andl
in the mnemonics for Acquire and reLease). I haven't re-read and thought through exactly what it's testing so maybe I'm missing the point you're making, but I did upvote it at the time after reading it carefully then and finding it convincing. – Wonderfulseq_cst
only differs fromrelaxed
in its ability to create "synchronized with" relationship (doesn't happen here, as it requires >1 non-relaxed operation on a variable), and in the seq-cst order affecting the values returned from loads (which that code doesn't examine, hence they too don't matter). – Tersanctus0,1
behavior. I find it easier to understand that behavior by thinking of atomic RMW as two operations. But you're also welcome to think of it as one operation for which the standard does not guarantee a particular ordering behavior that a person might naively think it would. – Crispix.exchange(1, std::memory_order_acq_rel);
is determined to happen before/afterint xx = x.load(std::memory_order_acquire);
, so it can read the initial0
. It does not matter whether the operation is RMW or just a store. – Sandsxx == 1
then we are assured that the exchange happens-before the load, because a release store synchronized with an acquire load. That is all we can say. But it has no bearing to the question of which outputs the program is permitted to generate. – Crispi0,1
, we would have to be able to show that wheneveryy == 1
, thenx.exchange
happens-beforex.load
. I claim there is no way to conclude that from the standard's ordering rules, regardless of whether you interpret the exchange as one operation or two. – Crispicompare_exchange_strong/weak
updates its first arg by reference). Otherwise, with #1 running first, #1 and the iteration of #2 that succeeds are both RMW operations. #1 is an RMW with release semantics, #2 is an RMW with acquire semantics. (Assuming that #2 is supposed to be the CAS inside the while loop; there is no #2 comment in the code block.) – Wonderful#2
only runs one iteration, which reads the value written by the RMW operation in thread 1. – Sandsrelease
andacquire
are both usable with an RMW. (Or does the standard not explicitly say that?) I don't think there's a real question here, just perhaps a desire for more precision in the formalism to avoid reliance on what basic things are considered "obvious". – Wonderful