Appearance
41. Blocking, Non-Blocking & Async I/O
The core question: what does your thread do while waiting for I/O?
1. Blocking I/O: the thread waits, doing nothing, until I/O completes. Simple to write, but 1000 connections = 1000 idle threads = huge memory overhead. Doesn't scale (traditional Apache thread-per-request).
2. Non-blocking I/O: the thread checks if data is ready and moves on if not (EAGAIN), polling repeatedly. Never fully blocked, but busy-polling burns CPU and the code gets more complex.
3. Async I/O: the thread registers a callback and moves on completely; the OS notifies it when done. Thread stays fully free, scales massively, but callback-driven code is harder to reason about (used in Node.js, Nginx).
Synchronous vs asynchronous (often confused): synchronous means the caller waits for the operation — both blocking and non-blocking I/O are synchronous (non-blocking just doesn't wait idly, but you still poll). Asynchronous means the caller moves on and is notified later — it never polls.
The C10K problem: handling 10,000 concurrent connections. Blocking (thread-per-connection) needs ~10,000 threads (~10GB of stacks, huge context-switch overhead). An async event loop needs one thread + 10,000 callbacks — tiny RAM, no context switching. epoll is the mechanism that makes this efficient (O(1) "which fds are ready" vs O(n) for select/poll).
| Technology Model | |
|---|---|
| Apache (thread-per-request) | Blocking |
| Redis | Non-blocking + epoll |
| Node.js | Async + event loop |
| Nginx | Async + epoll |
Python asyncio | Async |