Threads
Thread Sleep and Yield
The std::thread module provides four functions to pause execution, cooperate with the OS scheduler, or wait for a wakeup notification on the current thread: thread::sleep, thread::yield_now, thread::park, and thread::park_timeout.
Comparison of Thread Suspension Functions
| Function | Signature | OS Scheduler Behavior | Primary Use Case |
|---|---|---|---|
thread::sleep(dur) |
fn sleep(dur: Duration) |
Suspends the current thread for at least dur (consumes 0 CPU cycles while sleeping) |
Rate-limiting, retry backoff, periodic polling |
thread::yield_now() |
fn yield_now() |
Voluntarily gives up the remainder of the thread's current CPU time slice to other runnable threads | Contended spin-loops after std::hint::spin_loop() |
thread::park() |
fn park() |
Blocks the current thread until its Thread::unpark() token is made available (or returns immediately if already unparked) |
Custom synchronization primitives, work-stealing idle workers |
thread::park_timeout(dur) |
fn park_timeout(dur: Duration) |
Blocks until unparked or until dur elapses |
Bounded-wait custom condition polling |
Thread State Transitions in the OS Scheduler:
[ Running on CPU Core ]
|
+-- thread::yield_now() ------> [ Runnable Queue ] (Ready to be scheduled again immediately)
|
+-- thread::sleep(Duration) --> [ Sleeping / Blocked ] --(Timer expires)--> [ Runnable Queue ]
|
+-- thread::park() -----------> [ Parked / Blocked ] --(other calls .unpark())--> [ Runnable ]
Using thread::sleep, thread::yield_now, and std::hint::spin_loop
When waiting for a lock or condition in a low-level lock-free structure:
- For the first few iterations (nanoseconds), call
std::hint::spin_loop(), which emits a CPU pause instruction (pauseon x86_64,yieldon AArch64) to reduce pipeline power consumption without invoking an OS context switch. - If still contended, call
std::thread::yield_now()to yield the OS time slice. - For longer waits, block via
thread::park(), aCondvar, orthread::sleep(Duration)so the thread consumes zero CPU cycles:
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use std::time::{Duration, Instant};
fn main() {
let start = Instant::now();
let progress = AtomicUsize::new(0);
thread::scope(|s| {
s.spawn(|| {
thread::sleep(Duration::from_millis(5));
progress.store(100, Ordering::Release);
});
// Wait for `progress == 100` using polite yielding:
let mut spins = 0_u32;
while progress.load(Ordering::Acquire) < 100 {
if spins < 10 {
std::hint::spin_loop();
spins += 1;
} else {
thread::yield_now();
}
}
});
println!(
"completed after {:?} (progress = {})",
start.elapsed(),
progress.load(Ordering::Relaxed)
);
}
completed after 5.21ms (progress = 100)
Important Caveat: Never Call std::thread::sleep Inside an async fn!
std::thread::sleep blocks the entire underlying OS worker thread, freezing all other concurrent async tasks multiplexed onto that thread by the async runtime. In async Rust, always use the runtime's non-blocking timer (tokio::time::sleep(dur).await).