Note

epoll 高效的核心原因是什么?

从内核事件表、就绪队列和“只处理就绪集合”说明 epoll 相对 select/poll 的关键优势。

省流

epoll 相对 select/poll 更高效,核心不在“多了一个系统调用名字”,而在于:把监视集合长期放在内核里,等待时主要只返回并处理已经就绪的描述符,而不是每次带着全量集合进内核再全量扫描。

epoll_ctl 负责增删改某个 fd 的关注事件;epoll_wait 从就绪队列取出当前可处理的事件。大量连接处于空闲时,不会因为“监视了 10 万个 fd”就在每次等待时做 10 万次无意义检查。

次要但重要的配套因素包括:用户态与内核态之间不必每次拷贝整个监视集合;就绪事件可以按批返回;在边缘触发下还能进一步减少重复通知。这些都建立在“内核维护事件表 + 就绪队列”这一结构之上。

和 select/poll 的本质差别

select/poll 的典型路径:

每次调用:
  用户态准备完整监视集合
  -> 拷入内核
  -> 内核检查几乎全部 fd
  -> 写回结果
  -> 用户态再扫描一遍找就绪者

连接数 N 很大、就绪数 R 很小时,成本大致随 N 增长。

epoll 的典型路径:

初始化:创建 epoll 实例
连接到来/变化:epoll_ctl 更新内核事件表(按 fd)
等待:epoll_wait 取出就绪队列中的事件(规模近似 R)

空闲连接继续留在事件表中,但不进入每次等待的热点路径。

“核心”可以落成哪几句话?

  1. 内核侧持久化的监视结构:不必每次重传全量兴趣集合。
  2. 就绪队列:只把“现在可以处理”的 fd 交给应用。
  3. 复杂度与活跃事件更相关:在大量空闲连接场景下,避免 O(N) 全表扫描成为主成本。

没有这三点,单纯把接口改名不会带来同样的扩展性。

需要同时说清的边界

  • 若几乎所有 fd 每次都就绪,任何多路复用都要处理这批事件,epoll 的相对优势会缩小。
  • 应用若在用户态又对返回事件做错误设计(例如每次人为遍历全部连接),会抵消接口收益。
  • epoll 高效不等于可以忽略非阻塞 I/O、缓冲区管理和协议状态机;它解决的是“如何知道谁就绪”,不是业务逻辑本身。