Why Compare select() and poll()?
Both select() and poll() are used for I/O multiplexing — they let one process watch multiple file descriptors at the same time and react when any of them becomes ready for reading or writing. They do the same job, but they differ in API design, timeout precision, error reporting, portability, and performance. Understanding these differences helps you pick the right tool for your application.
This is one small but real difference between the two system calls.
- select() accepts a
struct timevalwhich gives timeout in seconds and microseconds (millionths of a second). - poll() accepts a single integer timeout in milliseconds (thousandths of a second).
So select() has finer timeout granularity on paper. But in practice, both are limited by the kernel’s software clock tick (usually 1ms to 10ms on Linux), so the difference rarely matters in real applications.
/* select() timeout example */
struct timeval tv;
tv.tv_sec = 1; /* 1 second */
tv.tv_usec = 500000; /* 500000 microseconds = 0.5 second */
/* total = 1.5 seconds */
select(nfds, &readfds, NULL, NULL, &tv);
/* poll() timeout example */
int timeout = 1500; /* 1500 milliseconds = 1.5 seconds */
poll(fds, nfds, timeout);
What happens if a file descriptor you are monitoring gets closed while you are watching it? The two system calls handle this very differently.
- Sets
POLLNVALbit inreventsfield - Each pollfd entry has its own revents
- You can see exactly which fd was closed
- Direct and precise error reporting
- Returns -1 with
errno = EBADF - No indication of which descriptor failed
- You must manually check each fd
- Indirect and less convenient
In practice this difference is not critical because a well-written application already tracks which file descriptors it has opened and closed. But poll() is cleaner here.
/* poll() - detecting closed fd using POLLNVAL */
#include <poll.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
struct pollfd fds[2];
fds[0].fd = 3; /* some open fd */
fds[0].events = POLLIN;
fds[1].fd = 99; /* this fd is closed / invalid */
fds[1].events = POLLIN;
int ret = poll(fds, 2, 1000);
if (ret > 0) {
if (fds[1].revents & POLLNVAL) {
printf("fd 99 is invalid or closed!\n");
}
}
return 0;
}
Historically, select() appeared on UNIX systems much earlier than poll() and was more widely supported. This made select() the safe portable choice for many years.
Today both are standardized under SUSv3 (Single UNIX Specification version 3) and available on all modern Linux, macOS, and UNIX-like systems. So portability is no longer a practical concern when choosing between them.
One caveat: the behavior of poll() still varies slightly across different UNIX implementations in edge cases (e.g., behavior on certain device types). If you are writing code that must run on many older or embedded UNIX variants, test carefully.
This is the most important practical difference. To understand it, you need to understand how each system call tells the kernel which file descriptors to watch.
How select() passes fd info to kernel
select() takes a parameter called nfds = (highest fd number + 1). The kernel must scan all positions from 0 to nfds-1 in the fd_set bitmask. Even if you are only monitoring fd number 999 and nothing else, the kernel still scans all 1000 positions.
How poll() passes fd info to kernel
poll() takes an array of struct pollfd containing exactly the file descriptors you care about. If you want to watch only fd 999, you pass an array with one entry. The kernel only checks that one entry.
The bottom line: when your application holds many open file descriptors but monitors only a few of them at a time (sparse case), poll() will be faster than select().
Note: Linux 2.6 added optimizations that narrowed this gap compared to Linux 2.4, but the conceptual difference remains.
/* Demonstrating sparse fd scenario with poll() */
#include <poll.h>
#include <stdio.h>
int main(void)
{
/*
* Suppose fd values 500 and 999 are the only ones we care about.
* With poll(), we just list them directly — kernel checks only 2 entries.
*/
struct pollfd fds[2];
fds[0].fd = 500; /* first fd of interest */
fds[0].events = POLLIN;
fds[0].revents = 0;
fds[1].fd = 999; /* second fd of interest */
fds[1].events = POLLIN;
fds[1].revents = 0;
int ret = poll(fds, 2, 5000); /* wait up to 5 seconds */
if (ret > 0) {
if (fds[0].revents & POLLIN)
printf("fd 500 is ready to read\n");
if (fds[1].revents & POLLIN)
printf("fd 999 is ready to read\n");
} else if (ret == 0) {
printf("Timeout — no fd became ready\n");
} else {
perror("poll");
}
return 0;
}
/*
* With select() for the same scenario, nfds must be 1000
* and the kernel scans all 1000 positions — wasteful!
*
* select() equivalent (less efficient):
* fd_set readfds;
* FD_ZERO(&readfds);
* FD_SET(500, &readfds);
* FD_SET(999, &readfds);
* select(1000, &readfds, NULL, NULL, &timeout); // kernel scans 0..999
*/
| Feature | select() | poll() |
|---|---|---|
| Timeout unit | Microseconds | Milliseconds |
| Closed fd detection | Returns -1, errno=EBADF (you must find which one) | Sets POLLNVAL in revents of the specific fd |
| Portability | Older, very consistent | SUSv3, minor variations on old systems |
| Dense fd set perf. | Similar to poll() | Similar to select() |
| Sparse fd set perf. | Slower (kernel scans all positions up to nfds) | Faster (kernel only checks listed fds) |
| Max fd limit | FD_SETSIZE (usually 1024) | No hard limit |
| fd_set reinit needed | Yes — before every call | No — just update events field |
Next: Problems with select() and poll() at scale — why they fail for large numbers of file descriptors and what comes next.
