Worker threads communicate by passing work items through a shared queue. An unbounded queue can grow without limit under a fast producer; a bounded queue applies backpressure, slow consumers naturally throttle producers. This pattern appears in thread pools, audio pipelines, and capstone 10.4.
Producer consumer roles
* Producers call push when data is ready.
* Consumers call pop when they can process another item.
* The bounded capacity forces producers to block when the buffer is full instead of allocating without end.
Mutex + condition variable recap
Lesson 9.2: protect std::queue mutations with a std::mutex. Lesson 9.3: use two condition variables:
* notEmpty: consumers wait here when the queue is empty.
* notFull: producers wait here when the queue is at capacity.
std::condition_variable::wait requires std::unique_lock (not lock_guard) because it must unlock the mutex while sleeping and re-lock before returning.
Spurious wakeups and predicates
Always call wait(lock, predicate) so the thread goes back to sleep if it woke without a state change. Lesson 9.3 explained spurious wakeups, your predicate checks !queue.empty() or queue.size() < maxCapacity.
Notification pattern
After push, notify notEmpty so a waiting consumer can proceed. After pop, notify notFull so a blocked producer can proceed. notify_one() is enough when any single waiter may proceed; use notify_all() only when you must wake every waiter (e.g. shutdown).
Testing with real threads
The template spawns a producer and consumer with deliberate timing. Run under ThreadSanitizer (Lesson 8.9) when your toolchain supports it to catch missing locks or data races.
The Challenge: Blocking Producers & Consumers
Implement push and pop so that:
1. All queue access is mutex-protected.
2. push blocks when the queue is full.
3. pop blocks when the queue is empty.
4. Waiters use predicates and wake the opposite side after state changes.
Complete the // TODO sections in the playground template.
If you get stuck, expand the reference solution below.