在 Linux 中,poll 和 select 都是用于 I/O 多路复用的系统调用,允许程序同时监控多个文件描述符(如套接字、管道等)的读写状态。尽管它们功能相似,但在实现、性能和扩展性上有显著区别。以下是两者的详细对比:
| 特性 | select | poll |
|---|---|---|
| 系统调用原型 | int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); |
int poll(struct pollfd *fds, nfds_t nfds, int timeout); |
| 文件描述符集合表示 | 使用 fd_set 位掩码(固定大小) |
使用 pollfd 结构体数组(动态) |
| 最大文件描述符限制 | 受 FD_SETSIZE 限制(通常 1024) |
无硬编码限制(取决于系统资源) |
| 性能 | 每次调用需重新设置 fd_set |
只需修改变化的 pollfd 条目 |
| 可读性 | 接口复杂,需手动管理位掩码 | 接口更清晰,直接操作结构体 |
select:
fd_set 位掩码(readfds, writefds, exceptfds)监控描述符。fd_set,因此下次调用需重新初始化。FD_SETSIZE 定义(默认 1024),修改需重新编译内核。poll:
pollfd 结构体数组,每个元素包含 fd、events(监控的事件)和 revents(实际发生的事件)。revents 字段返回事件,无需每次重置整个数组。select:
fd_set 从用户态拷贝到内核态,描述符多时开销较大。poll:
pollfd 数组指针,拷贝开销相对较小。FD_SETSIZE 限制,更适合高并发场景。select:
readfds)、可写(writefds)和异常(exceptfds)。poll:
POLLIN、POLLPRI、POLLOUT、POLLRDHUP 等),可通过 events 字段灵活组合。select 是 POSIX 标准,几乎所有系统支持。poll 在大多数现代 Unix 系统上可用,但某些旧系统(如早期 BSD)可能不支持。fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(sockfd, &read_fds);
struct timeval timeout = {5, 0}; // 5秒超时
int ret = select(sockfd + 1, &read_fds, NULL, NULL, &timeout);
if (ret > 0 && FD_ISSET(sockfd, &read_fds)) {
// sockfd 可读
}
struct pollfd fds[1];
fds[0].fd = sockfd;
fds[0].events = POLLIN;
int ret = poll(fds, 1, 5000); // 5秒超时
if (ret > 0 && (fds[0].revents & POLLIN)) {
// sockfd 可读
}
优先用 poll:
POLLRDHUP 检测对端关闭)。使用 select:
更高性能场景:
epoll(Linux 特有,适用于大量描述符且活跃比例低的场景)。select:简单但扩展性差,适合低并发或兼容性要求高的场景。poll:更灵活且无描述符限制,推荐大多数现代应用。epoll/kqueue:Linux/BSD 的高性能替代方案,需针对平台优化。通过理解两者的差异,可以根据实际需求选择最合适的 I/O 多路复用机制。