Skip to content

Commit 6511e46

Browse files
committed
std: Optimize thread park/unpark implementation
This is an adaptation of rust-lang/futures-rs#597 for the standard library. The goal here is to avoid locking a mutex on the "fast path" for thread park/unpark where you're waking up a thread that isn't sleeping or otherwise trying to park a thread that's already been notified. Mutex performance varies quite a bit across platforms so this should provide a nice consistent speed boost for the fast path of these functions.
1 parent b247805 commit 6511e46

File tree

1 file changed

+72
-15
lines changed

1 file changed

+72
-15
lines changed

src/libstd/thread/mod.rs

+72-15
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ use panic;
171171
use panicking;
172172
use str;
173173
use sync::{Mutex, Condvar, Arc};
174+
use sync::atomic::AtomicUsize;
175+
use sync::atomic::Ordering::SeqCst;
174176
use sys::thread as imp;
175177
use sys_common::mutex;
176178
use sys_common::thread_info;
@@ -694,6 +696,11 @@ pub fn sleep(dur: Duration) {
694696
imp::Thread::sleep(dur)
695697
}
696698

699+
// constants for park/unpark
700+
const EMPTY: usize = 0;
701+
const PARKED: usize = 1;
702+
const NOTIFIED: usize = 2;
703+
697704
/// Blocks unless or until the current thread's token is made available.
698705
///
699706
/// A call to `park` does not guarantee that the thread will remain parked
@@ -771,11 +778,27 @@ pub fn sleep(dur: Duration) {
771778
#[stable(feature = "rust1", since = "1.0.0")]
772779
pub fn park() {
773780
let thread = current();
774-
let mut guard = thread.inner.lock.lock().unwrap();
775-
while !*guard {
776-
guard = thread.inner.cvar.wait(guard).unwrap();
781+
782+
// If we were previously notified then we consume this notification and
783+
// return quickly.
784+
if thread.inner.state.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst).is_ok() {
785+
return
786+
}
787+
788+
// Otherwise we need to coordinate going to sleep
789+
let mut m = thread.inner.lock.lock().unwrap();
790+
match thread.inner.state.compare_exchange(EMPTY, PARKED, SeqCst, SeqCst) {
791+
Ok(_) => {}
792+
Err(NOTIFIED) => return, // notified after we locked
793+
Err(_) => panic!("inconsistent park state"),
794+
}
795+
loop {
796+
m = thread.inner.cvar.wait(m).unwrap();
797+
match thread.inner.state.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst) {
798+
Ok(_) => return, // got a notification
799+
Err(_) => {} // spurious wakeup, go back to sleep
800+
}
777801
}
778-
*guard = false;
779802
}
780803

781804
/// Use [`park_timeout`].
@@ -842,12 +865,30 @@ pub fn park_timeout_ms(ms: u32) {
842865
#[stable(feature = "park_timeout", since = "1.4.0")]
843866
pub fn park_timeout(dur: Duration) {
844867
let thread = current();
845-
let mut guard = thread.inner.lock.lock().unwrap();
846-
if !*guard {
847-
let (g, _) = thread.inner.cvar.wait_timeout(guard, dur).unwrap();
848-
guard = g;
868+
869+
// Like `park` above we have a fast path for an already-notified thread, and
870+
// afterwards we start coordinating for a sleep.
871+
// return quickly.
872+
if thread.inner.state.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst).is_ok() {
873+
return
874+
}
875+
let m = thread.inner.lock.lock().unwrap();
876+
match thread.inner.state.compare_exchange(EMPTY, PARKED, SeqCst, SeqCst) {
877+
Ok(_) => {}
878+
Err(NOTIFIED) => return, // notified after we locked
879+
Err(_) => panic!("inconsistent park_timeout state"),
880+
}
881+
882+
// Wait with a timeout, and if we spuriously wake up or otherwise wake up
883+
// from a notification we just want to unconditionally set the state back to
884+
// empty, either consuming a notification or un-flagging ourselves as
885+
// parked.
886+
let (_m, _result) = thread.inner.cvar.wait_timeout(m, dur).unwrap();
887+
match thread.inner.state.swap(EMPTY, SeqCst) {
888+
NOTIFIED => {} // got a notification, hurray!
889+
PARKED => {} // no notification, alas
890+
n => panic!("inconsistent park_timeout state: {}", n),
849891
}
850-
*guard = false;
851892
}
852893

853894
////////////////////////////////////////////////////////////////////////////////
@@ -914,7 +955,10 @@ impl ThreadId {
914955
struct Inner {
915956
name: Option<CString>, // Guaranteed to be UTF-8
916957
id: ThreadId,
917-
lock: Mutex<bool>, // true when there is a buffered unpark
958+
959+
// state for thread park/unpark
960+
state: AtomicUsize,
961+
lock: Mutex<()>,
918962
cvar: Condvar,
919963
}
920964

@@ -958,7 +1002,8 @@ impl Thread {
9581002
inner: Arc::new(Inner {
9591003
name: cname,
9601004
id: ThreadId::new(),
961-
lock: Mutex::new(false),
1005+
state: AtomicUsize::new(EMPTY),
1006+
lock: Mutex::new(()),
9621007
cvar: Condvar::new(),
9631008
})
9641009
}
@@ -998,10 +1043,22 @@ impl Thread {
9981043
/// [park]: fn.park.html
9991044
#[stable(feature = "rust1", since = "1.0.0")]
10001045
pub fn unpark(&self) {
1001-
let mut guard = self.inner.lock.lock().unwrap();
1002-
if !*guard {
1003-
*guard = true;
1004-
self.inner.cvar.notify_one();
1046+
loop {
1047+
match self.inner.state.compare_exchange(EMPTY, NOTIFIED, SeqCst, SeqCst) {
1048+
Ok(_) => return, // no one was waiting
1049+
Err(NOTIFIED) => return, // already unparked
1050+
Err(PARKED) => {} // gotta go wake someone up
1051+
_ => panic!("inconsistent state in unpark"),
1052+
}
1053+
1054+
// Coordinate wakeup through the mutex and a condvar notification
1055+
let _lock = self.inner.lock.lock().unwrap();
1056+
match self.inner.state.compare_exchange(PARKED, NOTIFIED, SeqCst, SeqCst) {
1057+
Ok(_) => return self.inner.cvar.notify_one(),
1058+
Err(NOTIFIED) => return, // a different thread unparked
1059+
Err(EMPTY) => {} // parked thread went away, try again
1060+
_ => panic!("inconsistent state in unpark"),
1061+
}
10051062
}
10061063
}
10071064

0 commit comments

Comments
 (0)