Blocking vs Non-Blocking I/O: Memory and Threading Trade-offs
Blocking I/O Model
When you call read() on a blocking socket, the kernel puts your thread to sleep until data arrives. The thread cannot do other work. With one thread per connection, 10,000 connections need 10,000 threads. Each thread consumes 1 MB stack space: 10 GB just for stacks.
Blocking is simple to program. Code reads linearly: call read, get data, process, repeat. Error handling is straightforward. But thread overhead limits scalability. Context switching 10,000 threads destroys performance even before memory exhaustion.
Non-blocking I/O Model
Non-blocking sockets return EAGAIN if no data is ready. The caller must try again later. This enables one thread to manage many connections by checking each in turn. If a connection has data, process it. If not, move to the next connection.
Raw polling is inefficient. Checking 10,000 connections in a loop wastes CPU. Event notification systems solve this: epoll, kqueue, and IOCP tell you which connections are ready. Check only ready connections, handle them, repeat. One thread can manage hundreds of thousands of connections.
Threading Implications
Blocking I/O requires many threads for concurrency. Non-blocking I/O enables few threads to handle many connections. The trade-off is complexity: non-blocking code must handle partial data, manage state machines, and coordinate event driven callbacks.