Design Bounded Blocking Queue - Problem
Design and implement a thread-safe bounded blocking queue that efficiently handles concurrent access from multiple producer and consumer threads.
Your queue should support the following operations:
BoundedBlockingQueue(int capacity)- Initialize the queue with a maximum capacityvoid enqueue(int element)- Add an element to the rear of the queue. If the queue is full, the calling thread should block until space becomes availableint dequeue()- Remove and return the element from the front of the queue. If the queue is empty, the calling thread should block until an element becomes availableint size()- Return the current number of elements in the queue
Key Requirements:
- The implementation must be thread-safe and handle concurrent access properly
- Producer threads will only call
enqueue() - Consumer threads will only call
dequeue() - Blocking behavior is essential - threads must wait when the queue is full/empty
- Do not use built-in blocking queue implementations
This is a fundamental concurrency problem that tests your understanding of synchronization primitives like locks, condition variables, and thread coordination.
Input & Output
example_1.py โ Basic Operations
$
Input:
capacity = 3
operations: [enqueue(1), enqueue(2), size(), dequeue(), size()]
โบ
Output:
[null, null, 2, 1, 1]
๐ก Note:
Create queue with capacity 3, add elements 1 and 2 (size becomes 2), remove element 1 from front (size becomes 1). Operations execute without blocking since queue is neither full nor empty.
example_2.py โ Blocking Behavior
$
Input:
capacity = 2
Thread 1: enqueue(1), enqueue(2), enqueue(3) [blocks on third]
Thread 2: dequeue() [returns 1, unblocks Thread 1]
โบ
Output:
Thread 1 completes enqueue(3), Thread 2 gets 1
๐ก Note:
Thread 1 fills the queue and blocks on the third enqueue. When Thread 2 dequeues an element, it creates space and Thread 1 can complete its operation.
example_3.py โ Empty Queue Blocking
$
Input:
capacity = 2
Thread 1: dequeue() [blocks on empty queue]
Thread 2: enqueue(5) [unblocks Thread 1]
โบ
Output:
Thread 1 gets 5 from dequeue()
๐ก Note:
Thread 1 tries to dequeue from an empty queue and blocks. When Thread 2 adds element 5, Thread 1 is unblocked and receives that element.
Visualization
Tap to expand
Understanding the Visualization
1
Setup
Initialize queue with mutex lock and two condition variables: notFull and notEmpty
2
Producer Waits
When queue is full, producer thread waits on notFull condition variable
3
Consumer Signals
After dequeue, consumer signals notFull to wake up waiting producers
4
Consumer Waits
When queue is empty, consumer thread waits on notEmpty condition variable
5
Producer Signals
After enqueue, producer signals notEmpty to wake up waiting consumers
Key Takeaway
๐ฏ Key Insight: Condition variables provide the perfect mechanism for thread coordination - they eliminate busy waiting while ensuring threads wake up immediately when their conditions can be satisfied, making the solution both efficient and scalable.
Time & Space Complexity
Time Complexity
O(1)
Each operation is O(1) with efficient blocking - no wasted cycles
โ Linear Growth
Space Complexity
O(n)
Space for queue elements plus small overhead for synchronization primitives
โก Linearithmic Space
Constraints
- 1 โค capacity โค 103
- 0 โค element โค 106
- At most 104 operations will be performed
- Must handle concurrent access from multiple threads
- Implementation must be thread-safe without using built-in blocking queues
๐ก
Explanation
AI Ready
๐ก Suggestion
Tab
to accept
Esc
to dismiss
// Output will appear here after running code