@@ -1021,13 +1021,23 @@ impl Drop for PanicGuard {
1021
1021
/// specifying a maximum time to block the thread for.
1022
1022
///
1023
1023
/// * 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.
1031
1041
///
1032
1042
/// The motivation for this design is twofold:
1033
1043
///
@@ -1058,33 +1068,47 @@ impl Drop for PanicGuard {
1058
1068
///
1059
1069
/// ```
1060
1070
/// use std::thread;
1061
- /// use std::sync::{Arc, atomic::{Ordering, AtomicBool} };
1071
+ /// use std::sync::atomic::{Ordering, AtomicBool};
1062
1072
/// use std::time::Duration;
1063
1073
///
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 );
1066
1076
///
1067
1077
/// 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);
1068
1082
/// // We want to wait until the flag is set. We *could* just spin, but using
1069
1083
/// // 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.
1072
1086
/// thread::park();
1073
1087
/// // We *could* get here spuriously, i.e., way before the 10ms below are over!
1074
1088
/// // But that is no problem, we are in a loop until the flag is set anyway.
1075
- /// println!("Thread unparked");
1076
1089
/// }
1077
1090
/// println!("Flag received");
1078
1091
/// });
1079
1092
///
1080
1093
/// // Let some time pass for the thread to be spawned.
1081
1094
/// thread::sleep(Duration::from_millis(10));
1082
1095
///
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
+ ///
1083
1105
/// // 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`
1085
1107
/// // 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.
1086
1110
/// // Hence there is no risk of a deadlock.
1087
- /// flag .store(true, Ordering::Relaxed );
1111
+ /// FLAG .store(true, Ordering::Release );
1088
1112
/// println!("Unpark the thread");
1089
1113
/// parked_thread.thread().unpark();
1090
1114
///
@@ -1494,10 +1518,14 @@ impl Thread {
1494
1518
/// ```
1495
1519
/// use std::thread;
1496
1520
/// use std::time::Duration;
1521
+ /// use std::sync::atomic::{AtomicBool, Ordering};
1522
+ ///
1523
+ /// static QUEUED: AtomicBool = AtomicBool::new(false);
1497
1524
///
1498
1525
/// let parked_thread = thread::Builder::new()
1499
1526
/// .spawn(|| {
1500
1527
/// println!("Parking thread");
1528
+ /// QUEUED.store(true, Ordering::Release);
1501
1529
/// thread::park();
1502
1530
/// println!("Thread unparked");
1503
1531
/// })
@@ -1506,6 +1534,15 @@ impl Thread {
1506
1534
/// // Let some time pass for the thread to be spawned.
1507
1535
/// thread::sleep(Duration::from_millis(10));
1508
1536
///
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
+ ///
1509
1546
/// println!("Unpark the thread");
1510
1547
/// parked_thread.thread().unpark();
1511
1548
///
0 commit comments