Unbounded Buffer
An unbounded buffer has no capacity limit, so producers never block — only consumers do, when the buffer is empty. Only one semaphore is needed: full, counting items available to consume.
Why is one semaphore enough?
Because the symmetry of the bounded case is broken — there’s no “buffer is full” condition to wait on. The producer side is pure
V: append, signal, return. ConsumersP(full)to wait for an item. The underlying storage (linked list, dynamically-growing array) handles capacity.
Minimal unbounded buffer
std::list<int> queue;
uSemaphore full(0); // count of items
_Task Producer {
void main() {
for ( int i = 0; i < 100; i += 1 ) {
queue.push_back( i ); // append (never blocks)
full.V(); // signal new item
}
}
};
_Task Consumer {
void main() {
for ( int i = 0; i < 100; i += 1 ) {
full.P(); // wait for item
int v = queue.front();
queue.pop_front();
}
}
};Multi-producer / multi-consumer
Add a mutex semaphore mut(1) around the list mutations. full still counts items; mut serializes push_back/pop_front.
Caveat — unbounded in practice
“Unbounded” means no programmer-imposed limit; physical memory is still finite. If the producer persistently outruns the consumer, you’ll eventually OOM. Use a bounded buffer whenever you need backpressure.