What does the `compare_exchange` fail ordering mean?


I'm a little confused on what the failure ordering means for the compare_exchange methods.

In Jon Gjenset's stream on atomics, he says:

The extra parameter [the failure ordering] is, imagine the compare_exchange looks at the memory, and goes, "This value has changed, so I didn't do the store". This ordering is what ordering should the load have, if the load indicates that you shouldn't store.

How can the compare_exchange know ahead of time if it will fail or not? Doesn't the load tell it whether to fail or not, so wouldn't it always use the ordering specified? Are the load and the store not "distinct" operations? (I know on ARM it uses load-linked/store-conditional)

Or does the CPU issue another load after a failure, and that load's ordering is dictated by the failure ordering?

Sorry for the barrage of questions, those thoughts are just where I am at the moment.

Thank you for your help!

It doesn't need to know ahead of time whether it fails or not. The compiler will pick some instruction (or several instructions) that has strong enough guarantees for the chosen ordering in both the success and failure case. That may mean that it chooses something with stronger requirements than you asked for, e.g. if you ask for acq-rel in success and relaxed in failure, it could pick an instruction that provides acq-rel/acq instead if there isn't an exact match.

Finally, it also affects which optimizations can happen. This is probably the only place it makes a difference on most platforms.


I really have no idea of how to reason about atomics “correctly”, but AFAIK, these orderings are about what you’d observe in other shared data, unrelated to the atomic value. (The data itself is unrelated, but maybe the reading itself would need to happen some sort of causal relationship to reads/writes to the atomic; I would need to do some more reading on how these things work.) Assuming that e.g. this is implemented by some sort of memory-fencing / telling the CPU to synchronize all data in caches or whatever… (see, I really don’t know what exactly we’re talking about, but hear me out anyway…), then I would imagine that, say compare_exchange(success: Acquire, failure: Acquire) vs. compare_exchange(success: Acquire, failure: Relaxed) would differentiate in that, with the former, after the compare_exchange happened, if it failed, you’d need to trigger a new “synchronization-of-caches-or-whatever” to make sure you can read any changes to other shared data (e.g. data behind some &UnsafeCell<…>, or other atomics / mutexes / etc…) so that any modifications to that shared data that a different thread that modified the atomic (and hence caused your compare_exchange failure in the first place) has made (before it modified the atomic; using Released ordering or stronger) will be visible to the current thread.

1 Like

Ahh ok! That matches the definitions in rust/library/core/sync/src/atomic.rs :slight_smile:

No worries, we are reaching the edge of my intuition as well. I think if the compare_exchange failed though, it would not store, so it wouldn't case any "synchronization-of-caches". Now that I think about it, setting a stronger fail ordering would prevent reorderings before the compare_exchange. Not sure when that would be useful though :thinking:, since subsequent operations would probably depend on the result.