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.
Key Terms in This Tutorial
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 |
Q1. What is the maximum number of file descriptors you can monitor with select()?select() is limited by the FD_SETSIZE constant, which is typically 1024 on Linux. You cannot use select() to monitor file descriptors with numbers 1024 or higher unless you recompile with a larger FD_SETSIZE. poll() has no such hard limit.
Q2. Why must you reinitialize the fd_set before each call to select(), but not before poll()?select() modifies the fd_set in place — it clears the bits for file descriptors that are NOT ready. So after each call, the fd_set no longer contains the original set of descriptors you wanted to watch. You must rebuild it before the next call. poll() uses a separate events field (what you want to watch) and revents field (what happened), so the events field is never modified by the kernel.
Q3. When does poll() outperform select() in terms of file descriptor scanning?When the descriptor set is sparse — meaning the maximum fd number is large but only a few descriptors are actually being monitored. In this case, poll() wins because the kernel only checks the explicitly listed fds. select() forces the kernel to scan every position from 0 to nfds-1 regardless of how many are actually monitored.
Q4. How does poll() report that a monitored file descriptor was closed?poll() sets the POLLNVAL bit in the revents field of the pollfd structure corresponding to that descriptor. This tells you exactly which fd is invalid. select() only returns -1 with errno set to EBADF, giving no information about which descriptor caused the error.
Q5. Are select() and poll() affected by the kernel’s software clock granularity?Yes. Even though select() provides microsecond-level timeout precision and poll() provides millisecond-level, both are ultimately limited by the kernel’s timer resolution (software clock tick). On a system with a 10ms tick, a timeout of 1ms will not fire exactly at 1ms — it may fire up to one full tick late. So the extra precision of select() may not be practically useful on most Linux systems.
Q6. You are building a proxy server that may hold 50,000 open connections. Would you use select() or poll() for I/O multiplexing? Why?Neither is ideal for 50,000 connections because both have O(n) scaling problems. However between the two, poll() is better because it has no FD_SETSIZE limit and handles sparse descriptor sets more efficiently. For 50,000 connections the correct choice is epoll, which is Linux-specific and scales in O(1) with respect to the number of active events rather than total monitored fds.
Q7. What does nfds mean in the select() call and why is it one greater than the highest fd?nfds tells the kernel how many bit positions to scan in the fd_set bitmask. It must be (highest_fd + 1) because fd numbering starts at 0. If the highest fd you are monitoring is 10, then there are 11 positions (0 through 10) and nfds must be 11. If you pass a smaller value, select() will not check the higher-numbered descriptors even if they are in the fd_set.
