Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

I/O Multiplexing — select, poll, epoll
OS

I/O Multiplexing — select, poll, epoll

Understand how the OS handles thousands of concurrent I/O connections efficiently using epoll.

I/O multiplexing allows a single thread to monitor multiple file descriptors (sockets, pipes, files) and react when any of them becomes ready for I/O. Essential for building high-concurrency network servers.

The Problem

Traditional blocking I/O: one thread per connection. With 10,000 connections, you need 10,000 threads — too many (stack memory, context switch overhead).

I/O multiplexing: one thread monitors all 10,000 connections and processes only the ones that are ready.

Three Generations

System callComplexityMax FDsEfficiency at scale
select()O(N) — scan all FDs1024 (FD_SETSIZE)Poor
poll()O(N) — scan all FDsUnlimitedPoor
epoll (Linux)O(1) — only ready FDsUnlimitedExcellent

select()

fd_set readfds;
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
select(sock + 1, &readfds, NULL, NULL, NULL);
if (FD_ISSET(sock, &readfds)) { /* ready */ }

Problems: (1) Limited to 1024 FDs. (2) Bitmask must be rebuilt every call. (3) Scans all FDs even if only one is ready.

poll()

Uses an array of pollfd structs — no hardcoded limit. But still O(N) — the kernel must walk the entire array.

epoll (Linux)

epoll is event-driven, not scan-based. Three system calls:

  • epoll_create1() — create an epoll instance
  • epoll_ctl() — register interest in specific FDs
  • epoll_wait() — block until any registered FD is ready

Only ready FDs are returned — O(number of ready FDs), not O(total FDs). This makes epoll scale to hundreds of thousands of connections.

kqueue (BSD/macOS), IOCP (Windows)

Equivalent event-driven mechanisms on other platforms.

Q: What problem does I/O multiplexing solve?

A: Without it, you need one thread per connection. A web server with 10,000 connections would need 10,000 threads — memory and context-switch overhead is prohibitive. Multiplexing lets one thread handle 10,000 connections by only processing those that are ready.

Q: Why is select() limited to 1024 FDs?

A: The fd_set bitmask uses a fixed-size array (typically 1024 bits). The FD_SETSIZE constant defines this limit. poll() removes this by using a dynamically-sized struct array instead.

Q: How does epoll achieve O(1) performance?

A: epoll uses a callback mechanism. When an FD is registered, the kernel installs a callback in the socket’s wait queue. When the socket becomes ready (data arrives), the callback adds the FD to a ready list. epoll_wait() just returns the ready list — no scanning needed.

Q: When would you still use select() or poll()?

A: On platforms that don’t support epoll/kqueue, for simple tools with few FDs, or for portability. libuv and libevent abstract these details — they select the best available mechanism per platform.

My Private Notes

Notes are auto-saved locally to this device.