An I/O selector lets one thread wait for activity across many file descriptors instead of dedicating one blocked thread to each connection.

Recommended reading order

The three families differ by where the application waits. A blocking thread waits inside the operation, a readiness API waits before the operation, and a completion API waits after submitting the operation.

Blocking I/O

Blocking I/O means the calling thread enters a system call and does not return until the operation can complete, fails, or is interrupted. A thread reading from a TCP socket can sleep until the peer sends bytes. A thread accepting connections can sleep until a client connects.

The programming model is simple because local variables and the call stack hold progress. The concurrency model is expensive when many operations are waiting: each blocked connection usually needs a thread or process that the kernel scheduler must track.

Readiness-Based I/O

Readiness-based I/O asks the kernel which file descriptors are ready for an operation. “Ready” does not mean the operation has already happened. It means the application should be able to call read, write, accept, or a similar operation without blocking immediately.

APIPlatformNotes
selectPOSIXUses fixed-size descriptor sets and requires rebuilding/scanning sets on each call.
pollPOSIXUses an array of pollfd records, avoiding select’s fixed bitset shape but still scans linearly.
epollLinuxKeeps an in-kernel interest list and ready list, which scales better for large descriptor sets.
kqueueBSD and macOSUses filters for file descriptors, timers, signals, and other event sources.

The event-loop shape is:

  1. Register descriptors and requested events.
  2. Wait for a batch of readiness notifications.
  3. Run non-blocking operations on the ready descriptors.
  4. Store any unfinished progress in per-connection state.
  5. Return to the selector.

Readiness APIs are strongest for sockets, pipes, timers, signals, and other descriptors whose readiness changes over time. They are not universal asynchronous file-I/O APIs.

Completion-Based I/O

Completion-based I/O asks the kernel to perform operations and later report which ones completed.

APIPlatformNotes
io_uringLinuxUses shared submission and completion rings for many operation types, including socket and regular-file operations.
I/O Completion Ports (IOCP)WindowsA completion mechanism for associating completed I/O with worker threads.

The event-loop shape is:

  1. Submit one or more operations.
  2. Wait for completion events.
  3. Match each completion to application state.
  4. Submit follow-up operations.

Completion APIs move more of the operation lifecycle into the kernel interface. The application still owns buffers, state, ordering constraints, cancellation, and backpressure.

Selector Threads And Worker Pools

Many runtimes combine a selector thread with a worker pool. The selector thread handles readiness or completion events. Worker threads handle CPU-bound work or blocking operations that the selector cannot safely perform inline.

This split exists because an event loop is only responsive while event handlers stay short. If an event handler performs a cold disk read, compression pass, database query, or long calculation, every other connection assigned to that loop waits behind it.

The older Tokio 0.1 scheduler used this broad shape: an I/O driver waited for readiness and tasks were scheduled around that driver.

Choosing A Model

ModelUse whenWatch out for
Blocking thread per connectionSimplicity matters more than very high concurrency.Thread stacks, context switches, scheduler overhead.
Readiness selectorMany descriptors are mostly waiting on network or event readiness.Explicit state machines and blocking filesystem/CPU work.
Completion interfaceMany operations can be submitted concurrently and completions can be batched.Queue depth, object lifetime, cancellation, and portability.

See also