外观
一句话答案
epoll 用红黑树存 fd,事件就绪通过回调加入链表,epoll_wait 只返回就绪 fd,O(1) 效率,支持 ET/LT 触发。
核心要点
该题曾归属模块 08(计算机网络),此处从 OS 内核实现角度给出更深入的回答。
三者都是 Linux 下 I/O 多路复用的实现,允许一个线程同时监听多个文件描述符(fd)上的 I/O 事件。
核心对比:
| 维度 | select | poll | epoll |
|---|---|---|---|
| fd 数量限制 | 有限制(FD_SETSIZE,默认 1024) | 无硬性限制(链表存储) | 无限制(红黑树存储) |
| 数据结构 | fd_set(位图) | pollfd 数组(链表) | 红黑树 + 就绪链表 |
| fd 传递方式 | 每次调用都要将 fd 集合从用户空间拷贝到内核空间 | 同 select | 通过 epoll_ctl 注册一次即可,内核维护 |
| 检测方式 | 内核遍历所有 fd 检查就绪状态,O(n) | 同 select,O(n) | 就绪 fd 通过回调加入就绪链表,epoll_wait 直接返回就绪列表,O(1) |
| 返回方式 | 返回就绪 fd 总数,用户需遍历整个 fd_set 找出哪些就绪 | 同 select | 直接返回就绪 fd 列表,无需遍历 |
| 触发模式 | 仅水平触发(LT) | 仅水平触发(LT) | 支持 LT(默认)和 ET(边缘触发) |
epoll 的三个核心 API:
c
// 1. 创建 epoll 实例(内核中创建红黑树 + 就绪链表)
int epfd = epoll_create(1);
// 2. 注册/修改/删除 fd 及其关注的事件(只需执行一次)
// 内核将 fd 加入红黑树,并注册回调函数
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
// 3. 等待就绪事件(阻塞直到有 fd 就绪或超时)
// 内核直接将就绪链表中的事件拷贝到用户空间
int n = epoll_wait(epfd, events, maxevents, timeout);epoll 为什么是 O(1) 的?
- 红黑树管理所有 fd:
epoll_ctl注册 fd 时插入红黑树,增删改查都是 O(log n)。 - 回调机制:当某个 fd 上有事件就绪(如数据到达),内核通过回调函数将该 fd 从红黑树摘下并加入就绪链表(rdllist)。
epoll_wait只返回就绪的 fd:直接从就绪链表拷贝到用户空间,无需遍历所有 fd。
水平触发(LT)vs 边缘触发(ET):
| 模式 | 触发条件 | 特点 |
|---|---|---|
| LT(默认) | 只要 fd 上有数据可读/可写,每次 epoll_wait 都会通知 | 编程简单,不会丢事件;但可能重复通知 |
| ET | 仅当 fd 状态变化时通知(如从无数据变为有数据) | 高效(减少通知次数);但必须一次读完所有数据(配合非阻塞 I/O),否则可能丢事件 |
Reactor 模式与 epoll 的结合(Netty/Redis 的核心模型):
主 Reactor 线程(Boss) 从 Reactor 线程(Worker)
│ │
│ epoll_wait 监听 accept 事件 │ epoll_wait 监听 read/write 事件
│ → 新连接到来 │ → 数据可读
│ → accept() 获取 client fd │ → read() 读取数据
│ → 将 client fd 注册到 Worker 的 epoll │ → 业务处理
│ │ → write() 回写响应- Redis(单 Reactor 单线程):一个线程通过 epoll 管理所有连接的读写。
- Netty(主从 Reactor 多线程):Boss 线程接受连接,Worker 线程池处理 I/O 和业务。
- Nginx(多进程 + epoll):每个 worker 进程独立使用 epoll 管理连接。
适用场景:
- 连接数少且活跃度高:select/poll 即可,简单通用。
- 连接数多但活跃度低(如 Web 服务器、推送服务):epoll 优势明显,Nginx、Redis、Netty 都使用 epoll。
跨平台 I/O 多路复用对比:
| OS | 实现 | 说明 |
|---|---|---|
| Linux | epoll | 最成熟,性能最优 |
| macOS / BSD | kqueue | 类似 epoll,也是事件驱动 |
| Windows | IOCP | 异步 I/O 模型(Proactor 模式) |
Java NIO 的 Selector 在不同平台底层自动选择对应实现(Linux 上是 epoll)。
五、Linux 实战
追问与易错
追问方向:
- LT 和 ET 区别?
- 为什么 ET 更高效?
- Nginx/Redis 用哪种?
易错点:
- ❌ epoll 没有缺点——连接少时 select 更简单
- ❌ ET 一定比 LT 快——编程更复杂
💡 记忆锚点
epoll三板斧:epoll_create建"监控室"(红黑树+就绪链表),epoll_ctl给每个fd装"报警器"(注册一次,回调机制),epoll_wait只看报警面板(就绪链表,O(1))。LT像门铃一直响直到你开门,ET只响一声你必须立刻处理完。Redis单线程靠它管万级连接,Nginx/Netty也都靠它。