Skip to content

Commit 476798d

Browse files
Rollup merge of #99587 - ibraheemdev:park-orderings, r=m-ou-se
Document memory orderings of `thread::{park, unpark}` Document `thread::park/unpark` as having acquire/release synchronization. Without that guarantee, even the example in the documentation can deadlock: ```rust let flag = Arc::new(AtomicBool::new(false)); let t2 = thread::spawn(move || { while !flag.load(Ordering::Acquire) { thread::park(); } }); flag.store(true, Ordering::Release); t2.thread().unpark(); // t1: flag.store(true) // t1: thread.unpark() // t2: flag.load() == false // t2 now parks, is immediately unblocked but never // acquires the flag, and thus spins forever ``` Multiple calls to `unpark` should also maintain a release sequence to make sure operations released by previous `unpark`s are not lost: ```rust let a = Arc::new(AtomicBool::new(false)); let b = Arc::new(AtomicBool::new(false)); let t2 = thread::spawn(move || { while !a.load(Ordering::Acquire) || !b.load(Ordering::Acquire) { thread::park(); } }); thread::spawn(move || { a.store(true, Ordering::Release); t2.thread().unpark(); }); b.store(true, Ordering::Release); t2.thread().unpark(); // t1: a.store(true) // t1: t2.unpark() // t3: b.store(true) // t3: t2.unpark() // t2 now parks, is immediately unblocked but never // acquires the store of `a`, only the store of `b` which // was released by the most recent unpark, and thus spins forever ``` This is of course a contrived example, but is reasonable to rely upon in real code. Note that all implementations of park/unpark already comply with the rules, it's just undocumented.
2 parents 97bf23d + 3acb1d2 commit 476798d

File tree

1 file changed

+21
-11
lines changed

1 file changed

+21
-11
lines changed

library/std/src/thread/mod.rs

+21-11
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ impl Drop for PanicGuard {
889889
/// it is guaranteed that this function will not panic (it may abort the
890890
/// process if the implementation encounters some rare errors).
891891
///
892-
/// # park and unpark
892+
/// # `park` and `unpark`
893893
///
894894
/// Every thread is equipped with some basic low-level blocking support, via the
895895
/// [`thread::park`][`park`] function and [`thread::Thread::unpark`][`unpark`]
@@ -910,14 +910,6 @@ impl Drop for PanicGuard {
910910
/// if it wasn't already. Because the token is initially absent, [`unpark`]
911911
/// followed by [`park`] will result in the second call returning immediately.
912912
///
913-
/// In other words, each [`Thread`] acts a bit like a spinlock that can be
914-
/// locked and unlocked using `park` and `unpark`.
915-
///
916-
/// Notice that being unblocked does not imply any synchronization with someone
917-
/// that unparked this thread, it could also be spurious.
918-
/// For example, it would be a valid, but inefficient, implementation to make both [`park`] and
919-
/// [`unpark`] return immediately without doing anything.
920-
///
921913
/// The API is typically used by acquiring a handle to the current thread,
922914
/// placing that handle in a shared data structure so that other threads can
923915
/// find it, and then `park`ing in a loop. When some desired condition is met, another
@@ -931,6 +923,23 @@ impl Drop for PanicGuard {
931923
///
932924
/// * It can be implemented very efficiently on many platforms.
933925
///
926+
/// # Memory Ordering
927+
///
928+
/// Calls to `park` _synchronize-with_ calls to `unpark`, meaning that memory
929+
/// operations performed before a call to `unpark` are made visible to the thread that
930+
/// consumes the token and returns from `park`. Note that all `park` and `unpark`
931+
/// operations for a given thread form a total order and `park` synchronizes-with
932+
/// _all_ prior `unpark` operations.
933+
///
934+
/// In atomic ordering terms, `unpark` performs a `Release` operation and `park`
935+
/// performs the corresponding `Acquire` operation. Calls to `unpark` for the same
936+
/// thread form a [release sequence].
937+
///
938+
/// Note that being unblocked does not imply a call was made to `unpark`, because
939+
/// wakeups can also be spurious. For example, a valid, but inefficient,
940+
/// implementation could have `park` and `unpark` return immediately without doing anything,
941+
/// making *all* wakeups spurious.
942+
///
934943
/// # Examples
935944
///
936945
/// ```
@@ -944,7 +953,7 @@ impl Drop for PanicGuard {
944953
/// let parked_thread = thread::spawn(move || {
945954
/// // We want to wait until the flag is set. We *could* just spin, but using
946955
/// // park/unpark is more efficient.
947-
/// while !flag2.load(Ordering::Acquire) {
956+
/// while !flag2.load(Ordering::Relaxed) {
948957
/// println!("Parking thread");
949958
/// thread::park();
950959
/// // We *could* get here spuriously, i.e., way before the 10ms below are over!
@@ -961,7 +970,7 @@ impl Drop for PanicGuard {
961970
/// // There is no race condition here, if `unpark`
962971
/// // happens first, `park` will return immediately.
963972
/// // Hence there is no risk of a deadlock.
964-
/// flag.store(true, Ordering::Release);
973+
/// flag.store(true, Ordering::Relaxed);
965974
/// println!("Unpark the thread");
966975
/// parked_thread.thread().unpark();
967976
///
@@ -970,6 +979,7 @@ impl Drop for PanicGuard {
970979
///
971980
/// [`unpark`]: Thread::unpark
972981
/// [`thread::park_timeout`]: park_timeout
982+
/// [release sequence]: https://en.cppreference.com/w/cpp/atomic/memory_order#Release_sequence
973983
#[stable(feature = "rust1", since = "1.0.0")]
974984
pub fn park() {
975985
let guard = PanicGuard;

0 commit comments

Comments
 (0)