457: Condition Variable Pattern
Tutorial
The Problem
A mutex protects shared state but provides no way for a thread to wait until the state changes. A consumer thread checking "is the queue empty?" must repeatedly check (busy-wait) or sleep — wasting CPU or adding latency. Condition variables solve this: a thread calls wait (which atomically releases the mutex and sleeps), and another thread calls notify_one or notify_all when the state changes. The waiting thread re-acquires the mutex and re-checks the condition. This is the foundation of thread synchronization: producer-consumer queues, barrier synchronization, and event notification.
Condition variables are used in thread::park, bounded channel internals, database row locking, async runtime wakers, and any producer-consumer pattern requiring efficient sleep-until-ready.
🎯 Learning Outcomes
Condvar enables efficient waiting for state changes without busy-waitingArc<(Mutex<State>, Condvar)> pair pattern for sharing condition variableswait_while(guard, |s| condition) atomically releases lock and blocks until condition is falsenotify_one vs. notify_all semanticsCode Example
#![allow(clippy::all)]
// 457. Condvar for thread notification
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use std::time::Duration;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_notify() {
let p = Arc::new((Mutex::new(false), Condvar::new()));
let pp = Arc::clone(&p);
let w = thread::spawn(move || {
thread::sleep(Duration::from_millis(5));
*pp.0.lock().unwrap() = true;
pp.1.notify_one();
});
let g = p.1.wait_while(p.0.lock().unwrap(), |&mut v| !v).unwrap();
assert!(*g);
w.join().unwrap();
}
}Key Differences
(Mutex<T>, Condvar) as a tuple; OCaml typically uses separate mutex and condition values.Condvar::wait_while handles the spurious wakeup loop automatically; OCaml requires explicit while loop.wait returns the MutexGuard when woken; OCaml requires re-locking explicitly after Condition.wait.thread::park + unpark is a simpler one-shot condition variable; OCaml has no equivalent.OCaml Approach
OCaml's Condition.t provides the same primitive: Condition.wait cond mutex atomically releases the mutex and waits; Condition.signal cond wakes one waiter; Condition.broadcast cond wakes all. The pattern let (mutex, cond) = ... and while not predicate do Condition.wait cond mutex done handles spurious wakeups. OCaml 4.x's Thread.create + Mutex + Condition is the standard synchronization toolkit.
Full Source
#![allow(clippy::all)]
// 457. Condvar for thread notification
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use std::time::Duration;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_notify() {
let p = Arc::new((Mutex::new(false), Condvar::new()));
let pp = Arc::clone(&p);
let w = thread::spawn(move || {
thread::sleep(Duration::from_millis(5));
*pp.0.lock().unwrap() = true;
pp.1.notify_one();
});
let g = p.1.wait_while(p.0.lock().unwrap(), |&mut v| !v).unwrap();
assert!(*g);
w.join().unwrap();
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_notify() {
let p = Arc::new((Mutex::new(false), Condvar::new()));
let pp = Arc::clone(&p);
let w = thread::spawn(move || {
thread::sleep(Duration::from_millis(5));
*pp.0.lock().unwrap() = true;
pp.1.notify_one();
});
let g = p.1.wait_while(p.0.lock().unwrap(), |&mut v| !v).unwrap();
assert!(*g);
w.join().unwrap();
}
}
Exercises
BoundedQueue<T> using Arc<(Mutex<VecDeque<T>>, Condvar, Condvar)> — one Condvar for "not empty" (unblocks consumers) and one for "not full" (unblocks producers). Test with 4 producers and 4 consumers.EventBroadcaster using Condvar::notify_all. When broadcast() is called, all waiting threads receive the event simultaneously. Verify with 10 waiting threads that all unblock on a single broadcast.Condvar::wait_timeout_while to implement a try_recv_timeout(timeout: Duration) -> Option<T> that waits for an item but gives up after the timeout. Verify it returns None when no producer sends within the timeout.