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
| select / poll | epoll / kqueue | |
|---|---|---|
| Interface | Pass the whole set of descriptors on every call | Register once, then wait — the kernel keeps the interest list (Linux epoll, BSD/macOS kqueue; Windows uses IOCP completions instead) |
| Cost per call | O(n) to copy and scan all descriptors, ready or not | O(ready) — only descriptors with events are returned |
| Descriptor limit | select: FD_SETSIZE (1024) and a bitmap; poll: no hard limit but still O(n) | Limited only by ulimit -n; tens of thousands is routine |
| Trigger mode | Level-triggered only | Level- or edge-triggered (EPOLLET, EV_CLEAR); edge mode needs you to drain until EAGAIN |
| Portability | POSIX everywhere, including Windows for sockets | Platform-specific; libraries (libuv, asio, Tokio, Java NIO) hide the difference |
| Who uses it | Small tools, portable code with a handful of descriptors | Node.js, nginx, Redis, Envoy, every 10k-connection server |
| Choose this when | A handful of descriptors, maximum portability, or a quick tool where O(n) per call is irrelevant. | Thousands of connections per thread, or any server whose descriptor count grows with users — the C10K answer. |