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
| Blocking I/O | Non-blocking / async I/O | |
|---|---|---|
| `read()` with no data | The thread sleeps in the kernel until data arrives | Returns EAGAIN/EWOULDBLOCK at once (non-blocking), or completes later via a notification (async) |
| Concurrency model | One thread per in-flight operation | One thread drives many operations via readiness (epoll/kqueue) or completion (IOCP, io_uring) |
| CPU while waiting | Zero — the thread is off the run queue | Zero if you wait on readiness; 100% if you spin retrying EAGAIN |
| Simplicity | Straight-line code; errors are return values | State machines, callbacks or coroutines; partial reads and writes are normal |
| Files vs sockets | Regular files always "block" on the page cache/disk on Linux — O_NONBLOCK does nothing for them | Sockets and pipes multiplex well; true async file I/O needs io_uring, Windows overlapped I/O, or a thread pool (libuv) |
| Failure mode | A slow peer holds a whole thread; thread pool exhaustion | A forgotten non-blocking flag turns the loop into a blocking loop; busy-poll at 100% CPU |
| Choose this when | Few concurrent operations, simple tools and scripts, or a dedicated thread per long-lived operation is affordable. | Many concurrent operations per thread — servers, proxies, event-loop runtimes — where waiting must not cost a thread. |