A consumer with nothing to do has two bad options: spin in a loop burning CPU, or sleep for a fixed time and add latency. Condvar is the third option. It parks the thread until another thread says the state has changed.
A condvar is always paired with a mutex. The mutex protects the data; the condvar carries the announcement.
let mut items = self.items.lock().unwrap();
while items.is_empty() {
items = self.ready.wait(items).unwrap();
}wait consumes the guard, releases the lock, parks the thread, and hands the guard back re-locked once it wakes. Giving up the lock is essential: the producer needs it to add anything.
The while is not a stylistic choice, and swapping it for an if is the classic bug. wait is allowed to return without any matching notification at all, and even a real notification can be stolen by another consumer that reached the lock first. Always re-check the condition after waking.
On the producer side:
self.items.lock().unwrap().push_back(item);
self.ready.notify_one();A notification sent while nobody is waiting is simply lost, which is the other half of why the predicate must be checked before waiting rather than after.
notify_one wakes a single waiter; notify_all wakes every one of them, which you want when the state change could satisfy several different predicates.
wait_timeout is the version that gives up. It returns the guard plus a WaitTimeoutResult, and the predicate still decides the outcome:
let (guard, result) =
self.ready.wait_timeout(items, timeout).unwrap();Implement a small blocking queue.
pub struct Queue<T> {
items: Mutex<VecDeque<T>>,
ready: Condvar,
}The items field is given. Add the Condvar and implement five methods.
new, len and is_emptypub fn new() -> Self
pub fn len(&self) -> usize
pub fn is_empty(&self) -> boolAn empty queue, the current number of waiting items, and whether that count is zero.
pushpub fn push(&self, item: T)Adds item to the back and wakes one waiting consumer. Note the &self: pushing takes no &mut, because the mutex provides the mutability.
poppub fn pop(&self) -> TRemoves and returns the oldest item, blocking until one exists. Items come out in the order they went in.
pop_timeoutpub fn pop_timeout(&self, timeout: Duration) -> Option<T>The same, but returns None if nothing arrives within timeout.
Condvar::new() takes no arguments and needs no relationship to the mutex declared up front.notify_one, or the thread you just woke immediately blocks again on the lock you are still holding.push can be one line plus the notify: self.items.lock().unwrap().push_back(item); ends the temporary guard's life at the semicolon.pop is: lock, while the deque is empty reassign the guard from self.ready.wait(guard).unwrap(), then pop_front().unwrap().pop_timeout, destructure let (guard, result) = self.ready.wait_timeout(guard, timeout).unwrap(); and return None when the queue is still empty and result.timed_out().len can lock and read; is_empty can just call len.A consumer with nothing to do has two bad options: spin in a loop burning CPU, or sleep for a fixed time and add latency. Condvar is the third option. It parks the thread until another thread says the state has changed.
A condvar is always paired with a mutex. The mutex protects the data; the condvar carries the announcement.
let mut items = self.items.lock().unwrap();
while items.is_empty() {
items = self.ready.wait(items).unwrap();
}wait consumes the guard, releases the lock, parks the thread, and hands the guard back re-locked once it wakes. Giving up the lock is essential: the producer needs it to add anything.
The while is not a stylistic choice, and swapping it for an if is the classic bug. wait is allowed to return without any matching notification at all, and even a real notification can be stolen by another consumer that reached the lock first. Always re-check the condition after waking.
On the producer side:
self.items.lock().unwrap().push_back(item);
self.ready.notify_one();A notification sent while nobody is waiting is simply lost, which is the other half of why the predicate must be checked before waiting rather than after.
notify_one wakes a single waiter; notify_all wakes every one of them, which you want when the state change could satisfy several different predicates.
wait_timeout is the version that gives up. It returns the guard plus a WaitTimeoutResult, and the predicate still decides the outcome:
let (guard, result) =
self.ready.wait_timeout(items, timeout).unwrap();Implement a small blocking queue.
pub struct Queue<T> {
items: Mutex<VecDeque<T>>,
ready: Condvar,
}The items field is given. Add the Condvar and implement five methods.
new, len and is_emptypub fn new() -> Self
pub fn len(&self) -> usize
pub fn is_empty(&self) -> boolAn empty queue, the current number of waiting items, and whether that count is zero.
pushpub fn push(&self, item: T)Adds item to the back and wakes one waiting consumer. Note the &self: pushing takes no &mut, because the mutex provides the mutability.
poppub fn pop(&self) -> TRemoves and returns the oldest item, blocking until one exists. Items come out in the order they went in.
pop_timeoutpub fn pop_timeout(&self, timeout: Duration) -> Option<T>The same, but returns None if nothing arrives within timeout.
Condvar::new() takes no arguments and needs no relationship to the mutex declared up front.notify_one, or the thread you just woke immediately blocks again on the lock you are still holding.push can be one line plus the notify: self.items.lock().unwrap().push_back(item); ends the temporary guard's life at the semicolon.pop is: lock, while the deque is empty reassign the guard from self.ready.wait(guard).unwrap(), then pop_front().unwrap().pop_timeout, destructure let (guard, result) = self.ready.wait_timeout(guard, timeout).unwrap(); and return None when the queue is still empty and result.timed_out().len can lock and read; is_empty can just call len.