Pacing the chefs so the waiters aren't overwhelmed.
In a concurrent system, one thread (Producer) creates data, and another thread (Consumer) processes it. They communicate via a queue. But what if the Producer generates 10,000 items a second, and the Consumer can only process 10? If the queue is infinite, the system will run out of RAM and crash. The solution is a Bounded Buffer: a queue with a maximum size limit. If the queue is full, the Producer goes to sleep until space opens up.
Almost every language standard library provides a Thread-Safe Bounded Queue (e.g., ArrayBlockingQueue in Java, Channels in Go). You don't have to write the sleep/wake logic yourself; the queue's put() and take() methods automatically block the calling thread when necessary.
# Go example: Channels are Bounded Buffers
// Create a buffer that holds exactly 3 items
jobs := make(chan string, 3)
// PRODUCER
func produce() {
jobs <- "Job A" // Puts job in.
jobs <- "Job B"
jobs <- "Job C"
// Buffer is now FULL!
jobs <- "Job D" // ⚠ Thread pauses here automatically until Consumer takes an item
}
// CONSUMER
func consume() {
// ⚠ Thread pauses here automatically if Buffer is EMPTY
job := <-jobs
process(job)
}
Bounded buffers apply natural Backpressure. If the system is overwhelmed, the producers are forced to slow down, saving the server from an Out-Of-Memory (OOM) crash. The cost is that if producers are handling network requests, slowing them down means HTTP requests will start timing out for your users.