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.
Process vs ThreadThreads vs Async / event loopConcurrency vs ParallelismMutex vs SemaphoreBlocking I/O vs Non-blocking / async I/Oselect / poll vs epoll / kqueueContainer vs Virtual machineStack vs HeapPipe vs Shared memoryOS page cache vs Application cache
| Threads | Async / event loop | |
|---|---|---|
| Who waits for I/O | The thread blocks in the kernel; the scheduler runs something else | Nobody: the loop registers interest (epoll/kqueue/IOCP) and resumes the task when data is ready |
| Cost per concurrent task | A stack (often MBs of virtual, tens of KB resident) plus a kernel task | A closure or a coroutine frame — hundreds of bytes to a few KB |
| Uses multiple cores | Yes, naturally (subject to the GIL in CPython) | One loop is one thread; scale with worker threads or multiple processes |
| Shared-state bugs | Races anywhere two threads touch memory; needs locks | Interleaving only at await points — fewer races, but a long synchronous task stalls everyone |
| Failure mode | Too many threads: context-switch thrash, memory for stacks, lock contention | Blocked loop: timers late, health checks fail, one core at 100% while others idle |
| Code shape | Straight-line blocking code | async/await, callbacks, or coroutines; every blocking call must be made non-blocking |
| Choose this when | CPU-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. |