Free — no signup required

Synchronous vs. Asynchronous I/O

1 min read

The Waiting Game

In the standard Unix model, I/O is synchronous (blocking). When your code calls read(fd, buf, size), your process stops executing. The kernel suspends it, context-switches to another process, and wakes your process up only when the data is ready. Your process was blocked — it consumed no CPU, but it also made no progress.

For a simple script reading one file, this is fine. For a web server handling 10,000 simultaneous connections, it is catastrophic. If each connection blocks for 10ms waiting for a database read, a server with 100 threads can handle only 10,000 requests per second — and that is assuming perfect conditions. Real workloads are bursty.

The solution is asynchronous I/O: submit an I/O request, continue doing other work, and receive a notification when the I/O completes. This decouples the number of concurrent connections from the number of threads, which is the foundation of high-performance servers.

The Three Generations

Linux has three generations of async I/O, each solving the limitations of the previous:

  1. select / poll (1980s–1990s): Polling-based. O(N) per check.
  2. epoll (Linux 2.6, 2002): Event-driven. O(1) per event.
  3. io_uring (Linux 5.1, 2019): Shared-memory ring buffers. Near-zero syscall overhead.

The following concepts cover each generation in detail.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free