Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mixing Relaxed and Release-Acquire Memory Orders

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.

like image 349
Cruz Jean Avatar asked Nov 06 '22 21:11

Cruz Jean


1 Answers

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.

like image 182
Peter Cordes Avatar answered Nov 15 '22 06:11

Peter Cordes