在 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 多路复用机制。