Condvar Signaling
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();Your Task
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.
Implement new, len and is_empty
pub 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.
Implement push
pub 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.
Implement pop
pub fn pop(&self) -> TRemoves and returns the oldest item, blocking until one exists. Items come out in the order they went in.
Implement pop_timeout
pub fn pop_timeout(&self, timeout: Duration) -> Option<T>The same, but returns None if nothing arrives within timeout.
Notes
Condvar::new()takes no arguments and needs no relationship to the mutex declared up front.- Drop the guard before calling
notify_one, or the thread you just woke immediately blocks again on the lock you are still holding. - Do not hold the lock across anything slow. The whole queue stalls while you do.
Hints
Click here to reveal hints
pushcan be one line plus the notify:self.items.lock().unwrap().push_back(item);ends the temporary guard's life at the semicolon.popis: lock,whilethe deque is empty reassign the guard fromself.ready.wait(guard).unwrap(), thenpop_front().unwrap().- For
pop_timeout, destructurelet (guard, result) = self.ready.wait_timeout(guard, timeout).unwrap();and returnNonewhen the queue is still empty andresult.timed_out(). lencan lock and read;is_emptycan just calllen.
Run to execute your code and see test results here.