Philipp Klaus Krause <
p...@spth.de> writes:
> For the atomic_compare-exchange family, e.g.
>
> _Bool atomic_compare_exchange_strong(volatile A *object,
> C *expected, C desired);
>
> the description states "Atomically, compares the contents of the
> memory pointed to by object for equality with that pointed to by
> expected, and if true, replaces the contents of the memory pointed
> to by object with desired, and if false, updates the contents of the
> memory pointed to by expected with that pointed to by object."
>
> This reads to me as if the write to *expected is also part of the
> atomic operation.
For what it's worth my reading agrees on this point: any update
of *expected is included in the indivisible atomic operation of
comparing and either replacing of *object or updating of *expected.
> This doesn't map well to compare-and-swap hardware instructions,
> where the content of *object would be written to a register, and
> later stored into *expected by a separate instruction.
What I think you mean is that it /might/ not map nicely onto an
unprotected compare-and-swap operation, depending on context.
More on that point given below.
> Of course, this difference only matters if it is observable. I
> think it is observable if just after the compare-and swap, a second
> thread writes *object, while the first thread has already done the
> comparison, but not yet updated *expected.
Yes, in the worst case the combination of compare-and-swap and
(possible) subsequent update of *expected needs to be wrapped in
a protective shell covered by an inter-thread mutex, to prevent
other threads from breaking the atomicity requirements. But
please note: in the worst case. See below.
> Then e.g. a thread thread could read the new value from
> *object, but the old one from *expected, which is not possible
> if atomic_compare_exchange_strong is fully atomic (i.e. also
> wrt. *expected).
Consider the following function (disclaimer: not compiled):
void
update_atomic( volatile A* it, C new_from_old( C ) ){
C old = { 0 }, new;
do {
new = new_from_old( old );
} while( ! atomic_compare_exchange_strong( it, &old, new ) );
}
An implementation may compile this function using an inline
compare-and-swap, with no surrounding protection of using an
inter-thread mutex. A surrounding mutex call is not needed,
because the variable 'old' cannot be affected by what other
threads are doing (at least not in a way that has defined
behavior), and implementations can know that based on the
code in the function definition.
> Is my understanding here correct (I'm not an expert on atomics)?
My comments above are made primarily based on what I believe the
C standard requires, and not so much on trying to discern what I
think your understanding may be. If what I've said helps your
understanding then that's great but my emphasis has been on
explicating the consequences of what is in the C standard.