Fundamentals 13 min read

Why Blocking I/O Slows Your Server and How Select, Poll, and Epoll Fix It

This article explains the drawbacks of traditional blocking network I/O, introduces non‑blocking reads, and walks through the evolution from multithreaded workarounds to kernel‑level multiplexing mechanisms such as select, poll, and epoll, showing code examples and performance considerations.

Liangxu Linux
Liangxu Linux
Liangxu Linux
Why Blocking I/O Slows Your Server and How Select, Poll, and Epoll Fix It

Traditional blocking I/O makes a server thread wait in two places: the accept call for new connections and the read call for incoming data. If a client stops sending data, the thread remains blocked and cannot serve other clients.

Blocking I/O Example

listenfd = socket();
bind(listenfd);
listen(listenfd);
while (1) {
    connfd = accept(listenfd); // blocks until a client connects
    int n = read(connfd, buf); // blocks until data arrives
    doSomeThing(buf);
    close(connfd);
}

When read blocks, the server cannot accept new connections, leading to resource starvation.

Naïve Non‑Blocking Attempt with Threads

One common trick is to spawn a new thread for each read operation, so the main thread can continue accepting connections. The OS still provides a blocking read, so this only moves the blockage to another thread.

while (1) {
    connfd = accept(listenfd); // blocks
    pthread_create(doWork);
}
void doWork() {
    int n = read(connfd, buf); // still blocks
    doSomeThing(buf);
    close(connfd);
}

A true non‑blocking read is obtained by setting the file descriptor to O_NONBLOCK before calling read. The call returns -1 immediately if no data is available.

fcntl(connfd, F_SETFL, O_NONBLOCK);
int n = read(connfd, buffer); // returns -1 if no data

I/O Multiplexing with select

The select system call lets the kernel examine a set of file descriptors and report which ones are ready for I/O, eliminating the need for a thread per connection.

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

Typical usage:

// Thread 1: accept connections and add them to a list
while (1) {
    connfd = accept(listenfd);
    fcntl(connfd, F_SETFL, O_NONBLOCK);
    fdlist.add(connfd);
}
// Thread 2: wait for readiness
while (1) {
    int nready = select(fdlist);
    for (fd in fdlist) {
        if (fd != -1) {
            read(fd, buf);
            if (--nready == 0) break;
        }
    }
}

Limitations of select:

Requires copying the descriptor set to kernel space, costly at high concurrency.

Performs a linear scan in the kernel, which is still synchronous.

Only returns the count of ready descriptors; the user must iterate the whole set.

poll – Removing the Descriptor Limit

poll

uses an array of struct pollfd and eliminates the 1024‑descriptor limit of select.

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
    int   fd;      // file descriptor
    short events; // events to monitor
    short revents;// events that occurred
};

epoll – Scalable Event Notification

epoll

addresses all three drawbacks of select by keeping the descriptor set inside the kernel, using edge‑triggered notifications, and returning only the ready descriptors.

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,
               int maxevents, int timeout);

With epoll, a single system call can monitor thousands of sockets, and the kernel only notifies the application about descriptors that actually have I/O events, removing unnecessary user‑space traversal.

Summary

Blocking I/O blocks the server thread on accept and read. User‑space tricks like spawning threads or looping over non‑blocking reads work but still incur many system calls. Kernel‑level multiplexing APIs—first select, then poll, and finally epoll —provide increasingly efficient ways to monitor many descriptors with minimal overhead.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

epollpollselectIO MultiplexingNon-blocking I/Oblocking I/O
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.