Classic Synchronization Problems & Concurrent Programming

the bounded buffer

A bounded buffer is the shared shelf at the heart of the producer-consumer problem: a queue with a fixed maximum capacity. 'Bounded' is the important word — it can hold at most N items, never more. Compare it to an unbounded buffer, which grows as large as it needs to. The bound is not a limitation to apologize for; it is a feature. It guarantees that memory use cannot blow up if producers outrun consumers, and it provides natural backpressure: when the buffer is full, producers are forced to wait, which automatically slows them to the consumers' pace.

The usual implementation is a circular (ring) buffer: a fixed array of N slots with two indices, an in pointer where the next item is written and an out pointer where the next item is read, each advancing modulo N so they wrap around the end of the array back to the start. The buffer is empty when in equals out and full when advancing in would collide with out (often tracked by also keeping a count). Inserting means writing buffer[in] = item and doing in = (in + 1) % N; removing means reading buffer[out] and doing out = (out + 1) % N. No data is shifted; the array is reused in place, which makes it fast and cache-friendly.

On its own the data structure is not thread-safe — it is just an array and two integers. It becomes a correct concurrent component only when paired with synchronization: counting semaphores or condition variables to block on full/empty, and a mutex to make each insert and remove atomic. Many systems offer this ready-made (Java's ArrayBlockingQueue, Go's buffered channels, ring buffers in audio drivers and network cards). The honest caveat: choosing the bound N is a real tuning decision — too small and producers stall needlessly, too large and you waste memory and hide bursty behavior that backpressure was supposed to expose.

A ring buffer of N = 8 slots: in = 5, out = 2 means 3 items are pending (indices 2, 3, 4). After a consumer removes one: out becomes 3. After a producer adds one: in becomes 6.

Indices wrap modulo N, so the array is reused in a circle — no element is ever moved.

A classic off-by-one trap: if you use in == out for both 'empty' and 'full', you cannot tell them apart. Common fixes are keeping an explicit count, or leaving one slot always empty (so a full buffer holds N − 1 items).

Also called
有界緩衝區ring buffercircular buffer環形緩衝區