Which concurrency model should this server use?

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.

Choosing a Concurrency Model for a Server▶ lab

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?

Thread per Request: The Model That Reads Like Ordinary Code

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?

Event-Driven Servers: Many Connections, One Loop

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?

Hybrid Runtimes: It Was Never Threads Versus Async

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?

UI Concurrency: One Thread Owns the Screen

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?

The Database Solves Concurrency For Its Data, Not For Your Memory

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?

What Changes When the Shared State Is on Another Machine

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?

A Mutex on Server A Does Nothing About Server B

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?