Producer-Consumer
Producer-consumer decouples work generation from processing through a shared bounded queue, smoothing rate mismatches and enabling parallelism with backpressure. It underpins thread pools and pipelines.
The producer-consumer pattern coordinates two groups of threads or tasks through a shared queue. Producers generate work items and enqueue them; consumers dequeue and process them. The queue decouples the two: producers and consumers run at their own pace, and the queue absorbs short-term rate mismatches. It is one of the most fundamental concurrency patterns.
How It Works
A thread-safe, usually bounded, queue (a buffer) sits between producers and consumers. When a producer adds an item to a full queue, it blocks (or applies backpressure) until space frees; when a consumer reads from an empty queue, it blocks until an item arrives. This coordination is implemented with condition variables, semaphores, monitors, or a built-in blocking queue (BlockingQueue in Java, channels in Go, asyncio.Queue in Python, BufferBlock in .NET).
Bounding the queue is important: an unbounded queue can hide a permanent rate mismatch and exhaust memory. A bounded queue turns overload into backpressure that flows back to producers, protecting the system. Multiple producers and multiple consumers can share one queue for parallelism.
When to Use It
Use producer-consumer whenever work is generated and processed at different or variable rates: ingest pipelines, request handlers feeding worker pools, log and metric processing, and parallelizing CPU-bound work behind an I/O-bound source. It is the backbone of thread pools and message-driven systems.
Trade-offs
The queue adds latency and memory, and choosing its bound is a tuning decision: too small starves consumers, too large delays backpressure. Deadlocks and lost wake-ups are classic hazards if condition signaling is wrong; prefer battle-tested queue primitives over hand-rolled locking. Ordering across multiple consumers is not guaranteed, and graceful shutdown (draining the queue, poison-pill signals) needs explicit design.
Related Patterns
Producer-consumer is the foundation of the thread-pool pattern (a pool of consumers draining a task queue), complements the actor-model (each actor has an inbox queue), and mirrors event-driven-architecture at the in-process level.
Example
A web crawler runs several producer threads that fetch pages and enqueue extracted URLs into a bounded blocking queue. A pool of consumer threads dequeues URLs, downloads them, and feeds new links back. When fetching outpaces processing, the bounded queue fills and producers block, naturally throttling the crawl to a sustainable rate.