@@ -1021,13 +1021,23 @@ impl Drop for PanicGuard {
10211021/// specifying a maximum time to block the thread for.
10221022///
10231023/// * The [`unpark`] method on a [`Thread`] atomically makes the token available
1024- /// if it wasn't already. Because the token is initially absent, [`unpark`]
1025- /// followed by [`park`] will result in the second call returning immediately.
1026- ///
1027- /// The API is typically used by acquiring a handle to the current thread,
1028- /// placing that handle in a shared data structure so that other threads can
1029- /// find it, and then `park`ing in a loop. When some desired condition is met, another
1030- /// thread calls [`unpark`] on the handle.
1024+ /// if it wasn't already. Because the token can be held by a thread even if it is currently not
1025+ /// parked, [`unpark`] followed by [`park`] will result in the second call returning immediately.
1026+ /// However, note that to rely on this guarantee, you need to make sure that your `unpark` happens
1027+ /// after all `park` that may be done by other data structures!
1028+ ///
1029+ /// The API is typically used by acquiring a handle to the current thread, placing that handle in a
1030+ /// shared data structure so that other threads can find it, and then `park`ing in a loop. When some
1031+ /// desired condition is met, another thread calls [`unpark`] on the handle. The last bullet point
1032+ /// above guarantees that even if the `unpark` occurs before the thread is finished `park`ing, it
1033+ /// will be woken up properly.
1034+ ///
1035+ /// Note that the coordination via the shared data structure is crucial: If you `unpark` a thread
1036+ /// without first establishing that it is about to be `park`ing within your code, that `unpark` may
1037+ /// get consumed by a *different* `park` in the same thread, leading to a deadlock. This also means
1038+ /// you must not call unknown code between setting up for parking and calling `park`; for instance,
1039+ /// if you invoke `println!`, that may itself call `park` and thus consume your `unpark` and cause a
1040+ /// deadlock.
10311041///
10321042/// The motivation for this design is twofold:
10331043///
@@ -1058,33 +1068,47 @@ impl Drop for PanicGuard {
10581068///
10591069/// ```
10601070/// use std::thread;
1061- /// use std::sync::{Arc, atomic::{Ordering, AtomicBool} };
1071+ /// use std::sync::atomic::{Ordering, AtomicBool};
10621072/// use std::time::Duration;
10631073///
1064- /// let flag = Arc::new( AtomicBool::new(false) );
1065- /// let flag2 = Arc::clone(&flag );
1074+ /// static QUEUED: AtomicBool = AtomicBool::new(false);
1075+ /// static FLAG: AtomicBool = AtomicBool::new(false );
10661076///
10671077/// let parked_thread = thread::spawn(move || {
1078+ /// println!("Thread spawned");
1079+ /// // Signal that we are going to `park`. Between this store and our `park`, there may
1080+ /// // be no other `park`, or else that `park` could consume our `unpark` token!
1081+ /// QUEUED.store(true, Ordering::Release);
10681082/// // We want to wait until the flag is set. We *could* just spin, but using
10691083/// // park/unpark is more efficient.
1070- /// while !flag2 .load(Ordering::Relaxed ) {
1071- /// println!("Parking thread");
1084+ /// while !FLAG .load(Ordering::Acquire ) {
1085+ /// // We can *not* use ` println!` here since that could use thread parking internally.
10721086/// thread::park();
10731087/// // We *could* get here spuriously, i.e., way before the 10ms below are over!
10741088/// // But that is no problem, we are in a loop until the flag is set anyway.
1075- /// println!("Thread unparked");
10761089/// }
10771090/// println!("Flag received");
10781091/// });
10791092///
10801093/// // Let some time pass for the thread to be spawned.
10811094/// thread::sleep(Duration::from_millis(10));
10821095///
1096+ /// // Ensure the thread is about to park.
1097+ /// // This is crucial! It guarantees that the `unpark` below is not consumed
1098+ /// // by some other code in the parked thread (e.g. inside `println!`).
1099+ /// while !QUEUED.load(Ordering::Acquire) {
1100+ /// // Spinning is of course inefficient; in practice, this would more likely be
1101+ /// // a dequeue where we have no work to do if there's nobody queued.
1102+ /// std::hint::spin_loop();
1103+ /// }
1104+ ///
10831105/// // Set the flag, and let the thread wake up.
1084- /// // There is no race condition here, if `unpark`
1106+ /// // There is no race condition here: if `unpark`
10851107/// // happens first, `park` will return immediately.
1108+ /// // There is also no other `park` that could consume this token,
1109+ /// // since we waited until the other thread got queued.
10861110/// // Hence there is no risk of a deadlock.
1087- /// flag .store(true, Ordering::Relaxed );
1111+ /// FLAG .store(true, Ordering::Release );
10881112/// println!("Unpark the thread");
10891113/// parked_thread.thread().unpark();
10901114///
@@ -1494,10 +1518,14 @@ impl Thread {
14941518 /// ```
14951519 /// use std::thread;
14961520 /// use std::time::Duration;
1521+ /// use std::sync::atomic::{AtomicBool, Ordering};
1522+ ///
1523+ /// static QUEUED: AtomicBool = AtomicBool::new(false);
14971524 ///
14981525 /// let parked_thread = thread::Builder::new()
14991526 /// .spawn(|| {
15001527 /// println!("Parking thread");
1528+ /// QUEUED.store(true, Ordering::Release);
15011529 /// thread::park();
15021530 /// println!("Thread unparked");
15031531 /// })
@@ -1506,6 +1534,15 @@ impl Thread {
15061534 /// // Let some time pass for the thread to be spawned.
15071535 /// thread::sleep(Duration::from_millis(10));
15081536 ///
1537+ /// // Wait until the other thread is queued.
1538+ /// // This is crucial! It guarantees that the `unpark` below is not consumed
1539+ /// // by some other code in the parked thread (e.g. inside `println!`).
1540+ /// while !QUEUED.load(Ordering::Acquire) {
1541+ /// // Spinning is of course inefficient; in practice, this would more likely be
1542+ /// // a dequeue where we have no work to do if there's nobody queued.
1543+ /// std::hint::spin_loop();
1544+ /// }
1545+ ///
15091546 /// println!("Unpark the thread");
15101547 /// parked_thread.thread().unpark();
15111548 ///
0 commit comments