Producer-Consumer Problem

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. Consumers P(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.