Compare

Side-by-side on the decisions that recur: process vs thread, threads vs async, mutex vs semaphore, blocking vs non-blocking I/O, container vs VM — with when to choose each.

ThreadsAsync / event loop
Who waits for I/OThe thread blocks in the kernel; the scheduler runs something elseNobody: the loop registers interest (epoll/kqueue/IOCP) and resumes the task when data is ready
Cost per concurrent taskA stack (often MBs of virtual, tens of KB resident) plus a kernel taskA closure or a coroutine frame — hundreds of bytes to a few KB
Uses multiple coresYes, naturally (subject to the GIL in CPython)One loop is one thread; scale with worker threads or multiple processes
Shared-state bugsRaces anywhere two threads touch memory; needs locksInterleaving only at await points — fewer races, but a long synchronous task stalls everyone
Failure modeToo many threads: context-switch thrash, memory for stacks, lock contentionBlocked loop: timers late, health checks fail, one core at 100% while others idle
Code shapeStraight-line blocking codeasync/await, callbacks, or coroutines; every blocking call must be made non-blocking
Choose this whenCPU-bound work, or blocking libraries you cannot replace, in a runtime with real parallel threads (C++, Rust, Java, Go).Many mostly-idle connections and I/O-bound work — Node/TypeScript, Python asyncio, C++ with asio or io_uring — with CPU work pushed to a worker pool.