Queues, Channels & Message Passing
Producer/consumer as the flagship pattern, bounded versus unbounded queues, backpressure as the conversation between a fast producer and a slow consumer, channels, message passing and the actor model — coordination without shared memory.
The flagship pattern of the domain: one side creates work, a queue holds it, another side runs it. Everything interesting is in the queue — how big it is, what happens when it is full or empty, and who is allowed to notice that the producer has stopped.
Q · When one part of a system creates work faster than another can run it, what exactly must the two sides agree on?
An unbounded queue is not a queue without backpressure. It is a queue whose backpressure mechanism is the OOM killer, whose signal is a process restart, and whose latency is unbounded long before memory runs out.
Q · If the producer is faster than the consumer and the queue has no limit, what actually stops it — and how does that failure present at 3 a.m.?
The conversation a slow component has to have with a fast one. Without it the chain is: producer rate exceeds consumer rate, queue grows, memory grows, latency rises, something dies. Backpressure is the design question of how the slow side says "not so fast" in a way the fast side is forced to hear.
Q · When a downstream stage cannot keep up, how does that fact travel back to whatever is producing the work — and what does the producer do about it?
A typed conduit with send, receive and — the part people get wrong — close. Capacity decides whether a send is a rendezvous or a buffered handoff; closing is how a consumer learns that no more data is coming, and getting it wrong is how pipelines hang on shutdown.
Q · What does a channel guarantee that a shared queue does not, and how does a receiver learn that the sender is finished?
Instead of synchronizing access to shared state, transfer the data and let exactly one party own it at a time. You give up zero-copy sharing and a single global ordering; you get a system with no shared mutable state to protect, which is a different and much smaller problem.
Q · Can we make the synchronization unnecessary by moving the data instead of protecting it — and what does that trade cost?
Private state, a mailbox, and strictly sequential message processing. Inside an actor there is no concurrency at all, which is why there are no locks — and the costs are message overhead, ordering that is weaker than it looks, and the fact that remote actors turn this into a distributed-systems problem.
Q · What do you get by making a piece of state single-threaded by construction, and what does the mailbox cost you?
Before you pick a queue implementation, write down the four requirements: thread safety under how many producers and consumers, what ordering you actually need, whether operations block, and what the capacity is. Most queue bugs are a requirement nobody stated, not an implementation that was wrong.
Q · What are the actual requirements on the queue between these two stages, and which of them is the one nobody wrote down?
Stop accepting, finish what is in flight, signal the consumers, wait for the workers, then exit. Every step is a decision — and what happens to the work still sitting in the queue is a policy you choose, not an accident you discover during a deploy.
Q · When this process is told to stop, what happens to the work in flight, the work in the queue, and the workers blocked waiting for more?