在Linux系统的IO多路复用编程中,select和poll都是用于监控多个文件描述符是否就绪的经典机制,两者都能实现单进程处理多个IO事件的需求,但在设计细节和适用场景上存在明显差异。

基本工作机制差异
select的工作核心是维护一个固定大小的位图数组来存储需要监控的文件描述符,这个数组的大小在编译时由FD_SETSIZE决定,默认通常是1024,意味着默认情况下select最多只能同时监控1024个文件描述符。调用select时,需要将整个位图从用户空间拷贝到内核空间,内核遍历位图检查每个描述符的就绪状态,之后再把修改后的位图拷贝回用户空间,用户还需要再次遍历位图找到就绪的描述符。
poll则使用动态数组存储需要监控的文件描述符,数组的每个元素是一个pollfd结构体,结构体中包含要监控的描述符、关注的事件以及返回的事件。poll没有默认的文件描述符数量限制,只要系统内存足够就可以监控更多的描述符。调用poll时,同样需要将整个结构体数组拷贝到内核空间,内核检查后修改每个元素的返回事件字段,再拷贝回用户空间,用户遍历数组即可找到就绪的描述符。
使用方式对比
select的使用示例
下面是select监控标准输入是否可读的简单示例:
#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>
int main() {
fd_set read_fds;
struct timeval timeout;
int ret;
// 清空描述符集合
FD_ZERO(&read_fds);
// 把标准输入(文件描述符0)加入监控集合
FD_SET(0, &read_fds);
// 设置超时时间为5秒
timeout.tv_sec = 5;
timeout.tv_usec = 0;
// 调用select,最大描述符+1,只监控读事件,不监控写和异常事件,设置超时
ret = select(1, &read_fds, NULL, NULL, &timeout);
if (ret < 0) {
perror("select error");
return 1;
} else if (ret == 0) {
printf("timeout, no data inputn");
} else {
// 检查标准输入是否就绪
if (FD_ISSET(0, &read_fds)) {
printf("stdin is ready for readingn");
}
}
return 0;
}
poll的使用示例
下面是poll实现相同功能的示例:
#include <stdio.h>
#include <poll.h>
#include <unistd.h>
int main() {
struct pollfd fds[1];
int ret;
// 配置要监控的描述符,监控读事件
fds[0].fd = 0; // 标准输入
fds[0].events = POLLIN;
fds[0].revents = 0;
// 调用poll,监控1个描述符,超时时间5000毫秒(5秒)
ret = poll(fds, 1, 5000);
if (ret < 0) {
perror("poll error");
return 1;
} else if (ret == 0) {
printf("timeout, no data inputn");
} else {
// 检查返回的事件是否包含读就绪
if (fds[0].revents & POLLIN) {
printf("stdin is ready for readingn");
}
}
return 0;
}
性能与适用场景差异
当监控的文件描述符数量较少时,select和poll的性能差异不大,但随着监控的描述符数量增加,两者的性能都会下降,不过poll的下降幅度相对更平缓,因为select每次都需要处理固定大小的位图,即使只监控少量描述符,也需要拷贝整个位图;而poll的数组大小只和实际需要监控的描述符数量相关。
另外select的位图需要每次调用前重新设置,因为内核会修改位图内容,而poll的结构体数组中,只有revents字段会被内核修改,events字段不需要每次重新设置,使用上更方便一些。
如果监控的描述符数量远小于1024,且需要兼容更老的系统,select是可选的方案;如果需要监控更多的描述符,或者希望使用更灵活的事件配置方式,poll会更合适。不过在更高性能的场景下,epoll通常是比两者更好的选择。
核心差异总结
| 对比维度 | select | poll |
|---|---|---|
| 文件描述符数量限制 | 默认1024,由FD_SETSIZE决定 | 无默认限制,受系统内存限制 |
| 数据结构 | 固定大小位图 | 动态pollfd结构体数组 |
| 参数重置要求 | 每次调用前需要重新设置位图 | 只需要重置revents字段,events可复用 |
| 事件类型支持 | 读、写、异常三类事件 | 支持更丰富的事件类型,如POLLIN、POLLOUT、POLLERR等 |
| 兼容性 | 几乎所有系统都支持 | 大部分类Unix系统支持,兼容性略弱于select |