Note

epoll 的水平触发和边缘触发有什么区别?

说明 LT 与 ET 的通知条件、读写循环要求,以及为何 ET 常配合非阻塞 I/O。

省流

epoll 有两种触发方式:水平触发(Level Triggered, LT) 和 边缘触发(Edge Triggered, ET)。默认是 LT。

LT:只要文件描述符仍处于可读或可写状态,epoll_wait 就可能再次返回该事件。即使上次没有把数据读完,下次仍会通知。编程模型更接近 select/poll,不易漏事件。

ET:只在状态发生变化时通知一次,例如“变为可读”或“变为可写”。若一次没有把缓冲区读空或写满,通常不会因为“仍然可读/可写”而反复通知,必须在本次处理中尽可能读完或写完。因此 ET 几乎总是配合非阻塞 socket,并在 EAGAIN/EWOULDBLOCK 时结束本轮循环。

LT 更不易出错;ET 能减少重复唤醒与重复检查,但实现要求更严。生产代码选哪种,取决于能否保证完整、正确的读写循环,而不是单纯追求“更高级”。

水平触发(LT)

条件可以理解为:内核看的是当前电平——缓冲区里还有数据可读,或发送缓冲区仍有空间可写,就认为事件仍然成立。

对端发来 100 字节
应用只读了 40 字节,缓冲区还剩 60 字节
下次 epoll_wait:仍可能再次报告“可读”

特点:

  • 漏处理一次通常还有机会被再次唤醒。
  • 可以配合阻塞或非阻塞 I/O,但高并发下阻塞仍可能拖住事件循环。
  • 同一就绪状态可能被多次报告,空转检查会多一些。

边缘触发(ET)

条件可以理解为:内核看的是边沿——从“不可读”变为“可读”,或从“不可写”变为“可写”时通知。

对端发来 100 字节,触发一次可读通知
应用只读了 40 字节就返回事件循环
若对端不再继续发数据:可能不会再次通知可读
剩余 60 字节会一直留在缓冲区,直到出现新的边沿或用其他方式发现

因此正确写法通常是:

  1. 把 socket 设为非阻塞。
  2. 在一次可读事件里循环 read,直到返回 EAGAIN。
  3. 在一次可写事件里循环 write,直到写完待发数据或返回 EAGAIN。
  4. 正确处理部分写、连接关闭、错误码。

为什么 ET 要非阻塞?

若在阻塞 socket 上为“读干净”而循环 read,读空后下一次 read 会阻塞,整个事件线程卡住,其他连接无法被处理。非阻塞下读空会立刻得到 EAGAIN,可以安全退出本轮。

使用建议

场景更常见的选择
逻辑简单、正确性优先LT
要压低重复唤醒、团队能写对 ET 循环ET + 非阻塞
监听 socket accept常用 LT,或在 ET 下循环 accept 直到 EAGAIN

两种模式都可以正确实现高并发服务。选 ET 的收益来自“少被同一状态反复吵醒”,成本是实现复杂度和漏读漏写风险。