Consider std::atomic<int> x(0)
. If I understand correctly, std::memory_order_relaxed
guarantees only that the operation happens atomically, but provides no guarantees of synchronization. So x.fetch_add(1, std::memory_order_relaxed)
1000 times from 2 threads will have an end result of 2000 always. However, the returned value of any one of those calls is not guaranteed to reflect the true current value (e.g. the 2000th increment could return 1700 as the previous value).
But - and here's my confusion - given that those increments are happening in parallel, what would x.load(std::memory_order_acquire)
return? Or x.fetch_add(1, std::memory_order_acq_rel)
? Do these return the true current value or do they have the same problem of out-of-date answers that relaxed ordering has due to the relaxed increments?
As far as I can tell, the standard only guarantees that releasing to an acquire (on the same variable) synchronizes and thus gives the true current value. So how can relaxed mix in with typical acquire-release semantics?
For instance, I've heard std::shared_ptr
's reference count is incremented in relaxed order and decremented in acq_rel order because it needs to ensure it has the true value in order to only delete the object once. Because of this I'm tempted to think they would give the true current value, but I can't seem to find any standardese to back it up.
ISO C++ guarantees that a modification order exists for every atomic object separately.
With seq_cst there's guaranteed to be a global order where all threads can agree on a
changing before b
or something. But for a single object, a modification order is guaranteed to exist even if some operations are relaxed.
The return values you from relaxed fetch_add
define / record what the modification order was. The 2000th increment returns 2000
by definition, that's how you know it was the 2000th.
As far as I can tell, the standard only guarantees that releasing to an acquire (on the same variable) synchronizes and thus gives the true current value.
Synchronizes-with is only necessary if you care about reading other values, e.g. one thread stores to a non-atomic array, then does a release-store like data_ready = 1;
. For a reader to safely read data from the array, they need to see data_ready != 0
with an acquire load, which means they can also see the effects of all earlier assignments in the thread that did the release-store.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With