Skip to content

Commit 4c2bc31

Browse files
committed
atomics: allow atomic and non-atomic reads to race
1 parent 6a2cd0d commit 4c2bc31

File tree

1 file changed

+36
-23
lines changed

1 file changed

+36
-23
lines changed

library/core/src/sync/atomic.rs

+36-23
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,35 @@
2424
//!
2525
//! ## Memory model for atomic accesses
2626
//!
27-
//! Rust atomics currently follow the same rules as [C++20 atomics][cpp], specifically `atomic_ref`.
28-
//! Basically, creating a *shared reference* to one of the Rust atomic types corresponds to creating
29-
//! an `atomic_ref` in C++; the `atomic_ref` is destroyed when the lifetime of the shared reference
30-
//! ends. A Rust atomic type that is exclusively owned or behind a mutable reference does *not*
31-
//! correspond to an “atomic object” in C++, since the underlying primitive can be mutably accessed,
32-
//! for example with `get_mut`, to perform non-atomic operations.
27+
//! Rust atomics currently follow the same rules as [C++20 atomics][cpp], specifically the rules
28+
//! from the [`intro.races`][cpp-intro.races] section, without the "consume" memory ordering. Since
29+
//! C++ uses an object-based memory model whereas Rust is access-based, a bit of translation work
30+
//! has to be done to apply the C++ rules to Rust: whenever C++ talks about "the value of an
31+
//! object", we understand that to mean the resulting bytes obtained when doing a read. When the C++
32+
//! standard talks about "the value of an atomic object", this refers to the result of doing an
33+
//! atomic load (via the operations provided in this module). A "modification of an atomic object"
34+
//! refers to an atomic store.
35+
//!
36+
//! The end result is *almost* equivalent to saying that creating a *shared reference* to one of the
37+
//! Rust atomic types corresponds to creating an `atomic_ref` in C++, with the `atomic_ref` being
38+
//! destroyed when the lifetime of the shared reference ends. The main difference is that Rust
39+
//! permits concurrent atomic and non-atomic reads to the same memory as those cause no issue in the
40+
//! C++ memory model, they are just forbidden in C++ because memory is partitioned into "atomic
41+
//! objects" and "non-atomic objects" (with `atomic_ref` temporarily converting a non-atomic object
42+
//! into an atomic object).
3343
//!
3444
//! [cpp]: https://en.cppreference.com/w/cpp/atomic
45+
//! [cpp-intro.races]: https://timsong-cpp.github.io/cppwp/n4868/intro.multithread#intro.races
3546
//!
3647
//! Each method takes an [`Ordering`] which represents the strength of
37-
//! the memory barrier for that operation. These orderings are the
38-
//! same as the [C++20 atomic orderings][1]. For more information see the [nomicon][2].
48+
//! the memory barrier for that operation. These orderings behave the
49+
//! same as the corresponding [C++20 atomic orderings][1]. For more information see the [nomicon][2].
3950
//!
4051
//! [1]: https://en.cppreference.com/w/cpp/atomic/memory_order
4152
//! [2]: ../../../nomicon/atomics.html
4253
//!
43-
//! Since C++ does not support mixing atomic and non-atomic accesses, or non-synchronized
44-
//! different-sized accesses to the same data, Rust does not support those operations either.
45-
//! Note that both of those restrictions only apply if the accesses are non-synchronized.
54+
//! Since C++ does not support non-synchronized differently-sized accesses to the same data, Rust
55+
//! does not support those operations either.
4656
//!
4757
//! ```rust,no_run undefined_behavior
4858
//! use std::sync::atomic::{AtomicU16, AtomicU8, Ordering};
@@ -52,27 +62,30 @@
5262
//! let atomic = AtomicU16::new(0);
5363
//!
5464
//! thread::scope(|s| {
55-
//! // This is UB: mixing atomic and non-atomic accesses
56-
//! s.spawn(|| atomic.store(1, Ordering::Relaxed));
57-
//! s.spawn(|| unsafe { atomic.as_ptr().write(2) });
65+
//! // This is UB: conflicting concurrent accesses.
66+
//! s.spawn(|| atomic.store(1, Ordering::Relaxed)); // atomic store
67+
//! s.spawn(|| unsafe { atomic.as_ptr().write(2) }); // non-atomic write
5868
//! });
5969
//!
6070
//! thread::scope(|s| {
61-
//! // This is UB: even reads are not allowed to be mixed
62-
//! s.spawn(|| atomic.load(Ordering::Relaxed));
63-
//! s.spawn(|| unsafe { atomic.as_ptr().read() });
71+
//! // This is fine: the accesses do not conflict (as none of them performs any modification).
72+
//! // In C++ this would be disallowed since creating an `atomic_ref` precludes
73+
//! // further non-atomic accesses, but Rust does not have that limitation.
74+
//! s.spawn(|| atomic.load(Ordering::Relaxed)); // atomic load
75+
//! s.spawn(|| unsafe { atomic.as_ptr().read() }); // non-atomic read
6476
//! });
6577
//!
6678
//! thread::scope(|s| {
6779
//! // This is fine, `join` synchronizes the code in a way such that atomic
68-
//! // and non-atomic accesses can't happen "at the same time"
69-
//! let handle = s.spawn(|| atomic.store(1, Ordering::Relaxed));
70-
//! handle.join().unwrap();
71-
//! s.spawn(|| unsafe { atomic.as_ptr().write(2) });
80+
//! // and non-atomic accesses can't happen "at the same time".
81+
//! let handle = s.spawn(|| atomic.store(1, Ordering::Relaxed)); // atomic store
82+
//! handle.join().unwrap(); // synchronize
83+
//! s.spawn(|| unsafe { atomic.as_ptr().write(2) }); // non-atomic write
7284
//! });
7385
//!
7486
//! thread::scope(|s| {
75-
//! // This is UB: using different-sized atomic accesses to the same data
87+
//! // This is UB: using differently-sized atomic accesses to the same data.
88+
//! // (It would be UB even if these are both loads.)
7689
//! s.spawn(|| atomic.store(1, Ordering::Relaxed));
7790
//! s.spawn(|| unsafe {
7891
//! let differently_sized = transmute::<&AtomicU16, &AtomicU8>(&atomic);
@@ -82,7 +95,7 @@
8295
//!
8396
//! thread::scope(|s| {
8497
//! // This is fine, `join` synchronizes the code in a way such that
85-
//! // differently-sized accesses can't happen "at the same time"
98+
//! // differently-sized accesses can't happen "at the same time".
8699
//! let handle = s.spawn(|| atomic.store(1, Ordering::Relaxed));
87100
//! handle.join().unwrap();
88101
//! s.spawn(|| unsafe {

0 commit comments

Comments
 (0)