Select、Poll、Epoll 之间有什么区别?
- 标签:操作系统
🏛 业务架构师
三者都是 IO 多路复用的内核机制,差异在**"如何找出就绪的 fd"**:
- select:fd 集合(位图,上限 1024)传入内核,内核线性扫描全部 fd,就绪返回后用户态再线性扫一遍找谁就绪——每次调用双向全量拷贝与扫描,连接多时开销平方级;
- poll:链表结构去掉 1024 上限,但仍是线性扫描,本质没变;
- epoll:事件驱动回调——内核在 fd 就绪时把它挂入就绪链表,epoll_wait 直接取就绪列表(O(1)),且 fd 只注册一次不用重复传入。连接数越多,与前两者的差距越大。
业务视角的算账:
- 10 万连接、活跃率 1%(1000 个在收发数据):select 每次要扫 10 万个 fd 找 1000 个就绪;epoll 直接返回那 1000 个——扫描成本与总连接数解耦,只与活跃数相关;
- 这个特性直接决定了长连接网关类业务的经济模型:推送系统 50 万连接只有 3% 活跃,用 epoll 一台机器扛住,用 select 要几十台——连接密度 = 机器成本,epoll 是分水岭;
- Java 体系里 NIO(Linux 下)就是 epoll,Netty 再封装一层;Redis 单线程能扛 10 万 QPS 也是 epoll 的功劳。
边界补充:连接数少且几乎全活跃时(如内部高吞吐服务几个连接满载跑),select/poll 并不输——epoll 的回调注册也有成本,没有全场景最优,只有按流量画像选型。水平触发 vs 边沿触发(LT/ET)的编程复杂度差异,也是 Netty 帮业务屏蔽掉的细节之一。
一句话:epoll 把"找就绪连接"的成本从 O(总连接) 降到 O(活跃连接),改写了高并发服务的成本公式。
🏗 系统架构师
三者都是 I/O 多路复用的系统调用,差异在"监听能力与数据结构效率":
- select:fd 集合用 bitmap(FD_SETSIZE 默认 1024 上限),每次调用把集合从用户态拷到内核态,内核线性扫描所有 fd,返回后用户还要遍历找就绪的。O(n),跨平台兼容性最好;
- poll:改用 pollfd 数组,突破 1024 限制,但仍是线性扫描 + 全量拷贝 + 用户遍历,O(n) 没有质变;
- epoll:Linux 专属,三个 syscall 分工——
epoll_create建实例、epoll_ctl注册(内核维护红黑树,只拷贝一次)、epoll_wait只返回就绪列表(就绪 fd 由回调挂到就绪链表)。O(1) 获取就绪集合,百万连接、少量活跃的场景性能无衰减。
关键差异总结:select/poll 每次调用都要"传全集、扫全集";epoll"注册一次、内核记着,只回就绪的"。另外 epoll 支持边缘触发 ET(只通知一次,要求非阻塞 + 读到 EAGAIN,减少事件次数)与水平触发 LT(默认,不处理反复提醒)。
工程关联:Nginx、Redis、Netty(Linux 下自动选 EpollEventLoop)高并发的底层答案都是 epoll;freeBSD 是 kqueue,Java NIO 的 Selector 在 Linux 上就是 epoll 的封装。理解了它就理解了"C10K 问题"的解法史。
💻 全栈开发专家
三者都是 I/O 多路复用的系统调用,差异决定各自能撑的连接规模:
| 维度 | select | poll | epoll |
|---|---|---|---|
| fd 上限 | 1024(FD_SETSIZE) | 无硬限制 | 无硬限制 |
| fd 传递 | 每次调用全量拷贝到内核 | 每次全量拷贝 | 注册一次(epoll_ctl),内核红黑树管理 |
| 就绪检测 | 线性扫描 O(n) | O(n) | 事件回调,O(1) 取就绪链表 |
| 触发模式 | LT | LT | LT + ET(边缘触发,配非阻塞 fd) |
epoll 高效的本质:把"每次都要告诉内核我关心谁 + 内核每次全量检查"改成"注册一次,fd 就绪时回调挂到就绪链表,epoll_wait 只取就绪的"——连接数 10 万但活跃 1 千时,select 要扫 10 万,epoll 只返回 1 千。
LT vs ET:LT 水平触发,只要还有数据没读完就一直提醒(编程简单);ET 边缘触发只在状态变化时通知一次,必须一次读到 EAGAIN(配合非阻塞 fd),少读就丢事件——Netty 用的 LT,Nginx 用 ET。
工程佐证:
- Redis 单线程 + epoll 扛 6 万+连接;Nginx、Netty(
EpollEventLoopGroup)同理; - select 的价值只剩跨平台(Windows);
- 我排查连接不上/读写卡住时,
ss -s看连接分布、strace 看 epoll_wait 返回,就是对这套机制的直接观测。