Skip to content
极高困难

一句话答案

epoll 用红黑树存 fd,事件就绪通过回调加入链表,epoll_wait 只返回就绪 fd,O(1) 效率,支持 ET/LT 触发。

核心要点

该题曾归属模块 08(计算机网络),此处从 OS 内核实现角度给出更深入的回答。

三者都是 Linux 下 I/O 多路复用的实现,允许一个线程同时监听多个文件描述符(fd)上的 I/O 事件。

核心对比:

维度selectpollepoll
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) 的?

  1. 红黑树管理所有 fdepoll_ctl 注册 fd 时插入红黑树,增删改查都是 O(log n)。
  2. 回调机制:当某个 fd 上有事件就绪(如数据到达),内核通过回调函数将该 fd 从红黑树摘下并加入就绪链表(rdllist)。
  3. 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实现说明
Linuxepoll最成熟,性能最优
macOS / BSDkqueue类似 epoll,也是事件驱动
WindowsIOCP异步 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也都靠它。