-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AtomicOptionBox::take should allow Acquire ordering? #9
Comments
Without |
No: That is always guaranteed by the fact that the operation is atomic at all, not by the ordering. |
Are you sure about that? (better yet, can you point to any sources confirming that?) I do not claim to be an expert, but my understanding is that atomicity only guarantees that other threads won't see a partially-changed value (i.e. they either see the value before your If you do a Here is an illustartion. Initial value:
Since |
Your claim fits the code you provided, but that isn't the code that's used by |
Atomic swap changes nothing for this question, because from the hardware point of view it consists of a load and a store. Viewing load and store as a single operation only makes difference for compare-and-swap. In this case the question is about ordering, not about atomicity. |
Here, I drawn a better illustration.
Because (A) is Release and both (B) and (C) are Acquire, both are guaranteed to see the effect of (A) (well, (B) also happens in the same thread as (A) so of course it sees the effect of (A), but it's not the point). Because the stores are Relaxed in (B) and (C), their relative ordering is undefined, so (C) is not guaranteed to see the effects of (B), and vice versa. My claim, again, is that it is possible to get ptr1 == ptr2 == &42. Why stores are Relaxed:
|
I didn't think I would need to cite a source for "atomic swap is an atomic operation", but I guess here we are. Unfortunately I wasn't able to find the exact location where the
|
Atomicity and memory ordering are orthogonal; please do not confuse the two. Atomicity means that the operation cannot be disrupted by any actions of other threads. Memory ordering governs when other threads are required to see the effect of your operation, and which effects of other threads' operations you are required to see. The x86's XCHG is irrelevant here, because Rust is not x86-specific. x86 is a strange bird among the architectures, as it is willing to enforce much more in terms of cache coherency than any other multi-core architecture (and XCHG issuing a cache sync across all physical CPUs is insane, and insanely expensive by the token of other architectures). The guarantees Rust developers have chosen are, fortunately, documented in Rust's load-and-store functions such as AtomicPtr::swap. They do not seem to support your view. |
They are indeed documented - and there is more documentation than just that on the individual functions. From the top-level std::sync::atomic docs, we have:
We must look to C++ for definitions, then. From a draft of the C++ standard (n.b. viewing the true standard requires you to pay loads of money, but I've been assured that these drafts are virtually identical in content):
and:
Just to verify that AtomicPtr::swap indeed counts as a read-modify-write operation, we can also find the documentation of atomic_ref::exchange, which I'll assume is the equivalent:
Hence, if two atomic swaps with None are performed on the same variable, they cannot both read Some: one of the writes must come before the other in the modification order of the atomic variable, and the second read must read the value written by the first write (which is None). |
@elidupree is correct. Atomicity guarantees that all threads observe operations on a single atomic object as happening in the same order. That is true even if all of those operations have relaxed memory ordering. Memory ordering determines what set of interleavings of operations on distinct objects are observable from different threads (which may or may not be consistent with each other). Acquire-release ordering guarantees that, if a thread does an acquire-ordered load or RMW, and it loads the value stored by a release-ordered load or RMW, everything that happened-before the release-ordered operation also happens-before an acquire-ordered operation that loads that value. Importantly for this crate, that means when release-storing/swapping in a pointer and then acquire-loading/swapping out that same pointer ensures that operations on the pointee that happened-before the release-store/swap also happen-before (and don't cause a data race with) operations on the pointee after the acquire-load/swap. If the storing thread swaps/stores a none/null, there is no pointee, so there are no operations on the pointee to ensure ordering on. I can imagine some use cases might use |
Currently,
AtomicOptionBox::take
only allowsAcqRel
andSeqCst
, just likeAtomicOptionBox::swap
. ButAtomicOptionBox::take
is always putting aNone
into the box, which doesn't reference any other data. It still needs to be Acquire (so that it can safely drop the possibly-not-None value that it takes out), but I don't think it needs to be Release as well.The text was updated successfully, but these errors were encountered: