Rust Condvar
A Condvar is a way for a thread to sleep until another thread changes shared state and wakes it. Checking the data in a loop burns the processor for nothing.
The blunt way: check in a loop
Without a Condvar, a waiting thread can only keep checking the state under its lock. The loop is correct, but the processor burns the whole wait:
while !*lock.lock().unwrap() {
// nothing has changed — check again at once, and again
}A Condvar travels with its Mutex
The pair is so common that it is usually kept together in one Arc. The Mutex guards the state. The Condvar guards the waiting:
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let (lock, cvar) = &*pair;wait gives up the lock while it sleeps
wait(guard) unlocks the Mutex and sleeps as one atomic step, then locks it again before returning. Always wait in a while loop, and re-check the condition after every wake:
let mut ready = lock.lock().unwrap();
while !*ready {
ready = cvar.wait(ready).unwrap();
}
// here, ready is true — and this thread holds the lock againThe other side: change the state, then ring the bell
{
let mut ready = lock.lock().unwrap();
*ready = true;
} // the guard drops here, freeing the lock before anyone wakes
cvar.notify_one(); // wake one waiter (notify_all wakes them all)wait_timeout gives the wait a way out
When waiting forever is not acceptable, wait_timeout returns after the duration — and its result tells you which way you woke. Here nobody ever rings the bell:
use std::sync::{Condvar, Mutex};
use std::time::Duration;
fn main() {
let pair = (Mutex::new(false), Condvar::new());
let guard = pair.0.lock().unwrap();
let (guard, result) = pair.1.wait_timeout(guard, Duration::from_millis(50)).unwrap();
println!("still ready = {}", *guard);
println!("timed out = {}", result.timed_out());
}Example
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
fn main() {
let slot = Arc::new((Mutex::new(None::<String>), Condvar::new()));
let producer_slot = Arc::clone(&slot);
let producer = thread::spawn(move || {
let (lock, cvar) = &*producer_slot;
*lock.lock().unwrap() = Some(String::from("report ready"));
cvar.notify_one();
});
let (lock, cvar) = &*slot;
let mut guard = lock.lock().unwrap();
while guard.is_none() {
guard = cvar.wait(guard).unwrap();
}
println!("consumer received: {}", guard.take().unwrap());
producer.join().unwrap();
println!("producer finished");
}Key points
- A Condvar turns waiting into sleeping: the thread rests until the state changes and a notify wakes it.
- A Condvar (condition variable) always pairs with a Mutex.
- You wait on a condition about the state the Mutex guards.
- You never wait on the Condvar alone.
- Wake-ups can be spurious.
- A wait may return with no notify at all.
- A faster waiter may also take the prize first.
- The while-loop re-check is what keeps both harmless.
- Notify after changing the state, not before.
- The waiter believes the state, not the bell.
- If the change already happened before it waited, its first check sees it.
- It never sleeps at all.
- Use notify_one() when one waiter can proceed.
- Use notify_all() when the change could release everyone.
- Waking everyone for a one-seat prize just makes them queue at the lock.
- A Condvar wakes whoever is waiting on this condition.
- When you cannot name the thread, you want the Condvar.
In this example, the shared slot is a Mutex holding an Option<String>, with a Condvar beside it inside one Arc. The consumer locks the slot, finds None, and waits. wait hands the lock back while it sleeps. That is exactly what lets the producer act. The producer locks the slot, stores "report ready", and calls notify_one. The consumer wakes and re-locks. It loops back to its check. This time the slot holds Some. It takes the message out. Suppose the producer had been faster and finished before the consumer even looked. The while check would have found Some at once. The consumer would never have waited at all. Trusting the state, not the bell, is what makes a wake-up impossible to miss.
Note
Parking (earlier in this section) wakes one thread you can name.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Why must a Condvar wait sit inside a while loop that re-checks the condition?