Problem Description
Implement a thread-safe, bounded blocking queue with methods to enqueue and dequeue elements. The enqueue operation adds an element to the front of the queue and blocks if the queue is full until space is available. The dequeue operation removes an element from the rear of the queue and blocks if the queue is empty until an element is available. A size method returns the current number of elements in the queue.
Key Insights
- Thread-safety requires proper synchronization (using locks/mutexes).
- Use two condition variables (or equivalent) to block threads when the queue is empty or full.
- The underlying data structure can be a deque (double-ended queue) for efficient insertions at the front and removals from the rear.
- Notify waiting consumer threads after an enqueue and waiting producer threads after a dequeue.
- Each operation (enqueue, dequeue, size) must use mutual exclusion to avoid race conditions.
Space and Time Complexity
Time Complexity: O(1) per operation (enqueue and dequeue take constant time). Space Complexity: O(capacity), where capacity is the maximum number of elements the queue can hold.
Solution
The approach is to use an underlying queue (such as a deque) and protect it with a lock. Two condition variables are used:
- not_full: to make producer threads wait when the queue is full.
- not_empty: to make consumer threads wait when the queue is empty. When enqueue is called, the thread acquires the lock, waits on not_full if necessary, adds the element at the front, and then notifies threads waiting on not_empty. When dequeue is called, the thread acquires the lock, waits on not_empty if necessary, removes an element from the rear, and then notifies threads waiting on not_full. The size method simply returns the current size of the underlying container under the lock.