Concurrency in Real Systems
Thread-per-request against event-driven against hybrid runtimes, UI thread constraints, how databases solve concurrency for data but not for your application memory, and the boundary where a local mutex stops meaning anything at all.
Thread-per-request, thread pool, event loop, async tasks, worker processes. This is a decision with inputs — how much of a request is waiting, how much is computing, how many connections are open at once, and how much complexity the team can carry — and the inputs select the answer.
Q · Which concurrency model should this server use, and what decides it?
Give each request its own thread and let it block. The code is straight-line, the stack trace is the request, and the debugger works. It is the best mental model in the list — and it degrades on three specific axes: memory per thread, scheduler cost at high thread counts, and one connection held per request.
Q · Why is the simplest server model also the one that stops working first, and at what point exactly?
Forty thousand sockets, one thread, and a readiness mechanism that says which of them can be served without blocking. The cost per idle connection drops to a buffer and a descriptor — and in exchange, every handler on that thread is now responsible for the latency of every other connection.
Q · How does one thread serve forty thousand connections, and what does that thread now owe everyone?
Every production server of any size runs an event loop for I/O, a worker pool for blocking and CPU work, and OS threads underneath both. "Threads or async" is a question about one layer of a stack that always has three, and the interesting engineering is at the boundaries between them.
Q · If every real server mixes models, what is actually being decided and where do the bugs live?
Every mainstream UI framework restricts view mutation to a single thread, and it is not laziness — a view tree read by a layout pass while another thread mutates it has no coherent state to draw. So background work runs elsewhere and returns results to that thread, and the whole discipline follows from a frame budget.
Q · Why do UI frameworks insist that only one thread may touch the view, and what does that force on everything else?
Transactions, locks and MVCC give you strong guarantees about rows. They give you nothing about the in-process cache you populated from those rows, the counter you kept in a variable, or the check you performed in application code between two statements. Knowing exactly where the guarantee ends is the lesson.
Q · My database handles concurrency. Which of my concurrency problems does that actually solve?
On one machine, coordination is shared memory: a lock is an instruction sequence and it either succeeds or you wait. Across machines there is no shared memory, only messages — which can be lost, delayed, duplicated or answered by a node that has already died. Locking stops being a primitive and becomes a protocol with failure modes.
Q · Which of my single-machine concurrency intuitions survive when the state is on another machine?
The single most common concurrency mistake in production systems: a lock, a counter, a cache or a "have we already done this?" flag that lives in one process's memory, protecting an invariant that spans every process. It works perfectly in development, where there is one instance, and fails the day you scale to two.
Q · My code is correctly locked and it still double-processed. What did the lock actually protect?