Rust Thread Parking
Parking is a thread switching itself off until a partner thread switches it back on. thread::sleep, by contrast, naps a thread for a fixed time, ready or not.
Park puts the current thread to sleep
A parked thread uses no CPU at all while it waits:
thread::park(); // sleep here until another thread unparks this oneUnpark wakes one named thread
You wake a thread through its handle — the same handle join() uses:
let worker = thread::spawn(move || { /* the waiting work */ });
worker.thread().unpark(); // wake that one threadAn unpark that comes first is not lost
Each thread carries one wake-up token. unpark sets it. park spends it. So an early unpark simply makes the next park return at once:
use std::thread;
fn main() {
thread::current().unpark();
thread::park();
println!("the early unpark was remembered");
}Always re-check your condition in a loop
park may also return for no reason — a spurious wakeup. The safe pattern checks the shared condition again after every wake:
while !ready.load(Ordering::Acquire) {
thread::park();
}
// reaching here means ready really is trueExample
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use std::thread;
fn main() {
let queue = Mutex::new(VecDeque::new());
let done = AtomicBool::new(false);
thread::scope(|s| {
let consumer = s.spawn(|| loop {
let item = queue.lock().unwrap().pop_front();
if let Some(n) = item {
println!("consumed item: {}", n);
} else if done.load(Ordering::Acquire) {
break;
} else {
thread::park();
}
});
for n in [10, 20] {
queue.lock().unwrap().push_back(n);
consumer.thread().unpark();
}
done.store(true, Ordering::Release);
consumer.thread().unpark();
});
println!("queue drained");
}Key points
- Parking is sleep with a named waker: the thread waits until another thread unparks it.
- Use thread::park() when a thread should sleep until something specific happens.
- thread::sleep is for a fixed nap.
- park is for "wake me when it changes".
- Wake a thread with .unpark() on its Thread handle: handle.thread() from a JoinHandle, or thread::current() from inside the thread itself
- The token holds at most one wake-up: ten unparks in a row still buy only one instant return from park
- Use thread::park_timeout(duration) when the wait needs a limit, so a forgotten unpark cannot hang the thread forever
- Parking is a building block for your own waiting tools.
- It sits under many synchronisation primitives.
- For everyday work a channel or a Condvar is usually clearer.
- Writes made before an unpark are visible to the thread after its park returns.
- The pair behaves like a Release store meeting an Acquire load.
In this example, a consumer thread and the main thread share a queue. The queue is a Mutex around a VecDeque. They also share a done flag. The consumer pops items and prints them: 10, then 20. When the queue is empty and done is still false, it parks. It does not spin in a busy loop. The item was popped before parking. So no lock is held while it sleeps. Each time main pushes an item, it unparks the consumer. The token means no wake-up is ever lost. That holds even for one that arrives before the consumer has parked. At the end, main sets done with Release and unparks once more. The consumer wakes and sees the flag. It breaks its loop. Then the scope can join it. The final line prints after the scope ends, which fixes the order on screen: both items, then queue drained. The Condvar comes later in this section.
Note
An unpark also orders memory.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
A thread calls thread::park() and no other thread ever unparks it. What happens?