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 字节会一直留在缓冲区,直到出现新的边沿或用其他方式发现
因此正确写法通常是:
- 把 socket 设为非阻塞。
- 在一次可读事件里循环
read,直到返回EAGAIN。 - 在一次可写事件里循环
write,直到写完待发数据或返回EAGAIN。 - 正确处理部分写、连接关闭、错误码。
为什么 ET 要非阻塞?
若在阻塞 socket 上为“读干净”而循环 read,读空后下一次 read 会阻塞,整个事件线程卡住,其他连接无法被处理。非阻塞下读空会立刻得到 EAGAIN,可以安全退出本轮。
使用建议
| 场景 | 更常见的选择 |
|---|---|
| 逻辑简单、正确性优先 | LT |
| 要压低重复唤醒、团队能写对 ET 循环 | ET + 非阻塞 |
监听 socket accept | 常用 LT,或在 ET 下循环 accept 直到 EAGAIN |
两种模式都可以正确实现高并发服务。选 ET 的收益来自“少被同一状态反复吵醒”,成本是实现复杂度和漏读漏写风险。