std: Optimize thread park/unpark implementation

This is an adaptation of alexcrichton/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.
This commit is contained in:
Alex Crichton
2017-10-25 08:35:51 -07:00
parent b2478052f8
commit 6511e46753

View File

@@ -171,6 +171,8 @@ use panic;
use panicking; use panicking;
use str; use str;
use sync::{Mutex, Condvar, Arc}; use sync::{Mutex, Condvar, Arc};
use sync::atomic::AtomicUsize;
use sync::atomic::Ordering::SeqCst;
use sys::thread as imp; use sys::thread as imp;
use sys_common::mutex; use sys_common::mutex;
use sys_common::thread_info; use sys_common::thread_info;
@@ -694,6 +696,11 @@ pub fn sleep(dur: Duration) {
imp::Thread::sleep(dur) imp::Thread::sleep(dur)
} }
// constants for park/unpark
const EMPTY: usize = 0;
const PARKED: usize = 1;
const NOTIFIED: usize = 2;
/// Blocks unless or until the current thread's token is made available. /// Blocks unless or until the current thread's token is made available.
/// ///
/// A call to `park` does not guarantee that the thread will remain parked /// A call to `park` does not guarantee that the thread will remain parked
@@ -771,11 +778,27 @@ pub fn sleep(dur: Duration) {
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
pub fn park() { pub fn park() {
let thread = current(); let thread = current();
let mut guard = thread.inner.lock.lock().unwrap();
while !*guard { // If we were previously notified then we consume this notification and
guard = thread.inner.cvar.wait(guard).unwrap(); // return quickly.
if thread.inner.state.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst).is_ok() {
return
}
// Otherwise we need to coordinate going to sleep
let mut m = thread.inner.lock.lock().unwrap();
match thread.inner.state.compare_exchange(EMPTY, PARKED, SeqCst, SeqCst) {
Ok(_) => {}
Err(NOTIFIED) => return, // notified after we locked
Err(_) => panic!("inconsistent park state"),
}
loop {
m = thread.inner.cvar.wait(m).unwrap();
match thread.inner.state.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst) {
Ok(_) => return, // got a notification
Err(_) => {} // spurious wakeup, go back to sleep
}
} }
*guard = false;
} }
/// Use [`park_timeout`]. /// Use [`park_timeout`].
@@ -842,12 +865,30 @@ pub fn park_timeout_ms(ms: u32) {
#[stable(feature = "park_timeout", since = "1.4.0")] #[stable(feature = "park_timeout", since = "1.4.0")]
pub fn park_timeout(dur: Duration) { pub fn park_timeout(dur: Duration) {
let thread = current(); let thread = current();
let mut guard = thread.inner.lock.lock().unwrap();
if !*guard { // Like `park` above we have a fast path for an already-notified thread, and
let (g, _) = thread.inner.cvar.wait_timeout(guard, dur).unwrap(); // afterwards we start coordinating for a sleep.
guard = g; // return quickly.
if thread.inner.state.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst).is_ok() {
return
}
let m = thread.inner.lock.lock().unwrap();
match thread.inner.state.compare_exchange(EMPTY, PARKED, SeqCst, SeqCst) {
Ok(_) => {}
Err(NOTIFIED) => return, // notified after we locked
Err(_) => panic!("inconsistent park_timeout state"),
}
// Wait with a timeout, and if we spuriously wake up or otherwise wake up
// from a notification we just want to unconditionally set the state back to
// empty, either consuming a notification or un-flagging ourselves as
// parked.
let (_m, _result) = thread.inner.cvar.wait_timeout(m, dur).unwrap();
match thread.inner.state.swap(EMPTY, SeqCst) {
NOTIFIED => {} // got a notification, hurray!
PARKED => {} // no notification, alas
n => panic!("inconsistent park_timeout state: {}", n),
} }
*guard = false;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -914,7 +955,10 @@ impl ThreadId {
struct Inner { struct Inner {
name: Option<CString>, // Guaranteed to be UTF-8 name: Option<CString>, // Guaranteed to be UTF-8
id: ThreadId, id: ThreadId,
lock: Mutex<bool>, // true when there is a buffered unpark
// state for thread park/unpark
state: AtomicUsize,
lock: Mutex<()>,
cvar: Condvar, cvar: Condvar,
} }
@@ -958,7 +1002,8 @@ impl Thread {
inner: Arc::new(Inner { inner: Arc::new(Inner {
name: cname, name: cname,
id: ThreadId::new(), id: ThreadId::new(),
lock: Mutex::new(false), state: AtomicUsize::new(EMPTY),
lock: Mutex::new(()),
cvar: Condvar::new(), cvar: Condvar::new(),
}) })
} }
@@ -998,10 +1043,22 @@ impl Thread {
/// [park]: fn.park.html /// [park]: fn.park.html
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
pub fn unpark(&self) { pub fn unpark(&self) {
let mut guard = self.inner.lock.lock().unwrap(); loop {
if !*guard { match self.inner.state.compare_exchange(EMPTY, NOTIFIED, SeqCst, SeqCst) {
*guard = true; Ok(_) => return, // no one was waiting
self.inner.cvar.notify_one(); Err(NOTIFIED) => return, // already unparked
Err(PARKED) => {} // gotta go wake someone up
_ => panic!("inconsistent state in unpark"),
}
// Coordinate wakeup through the mutex and a condvar notification
let _lock = self.inner.lock.lock().unwrap();
match self.inner.state.compare_exchange(PARKED, NOTIFIED, SeqCst, SeqCst) {
Ok(_) => return self.inner.cvar.notify_one(),
Err(NOTIFIED) => return, // a different thread unparked
Err(EMPTY) => {} // parked thread went away, try again
_ => panic!("inconsistent state in unpark"),
}
} }
} }