epoll 是 linux 系统中提供的多路复用机制,现代服务器高并发的秘诀就在于使用 epoll。相较于 select 和 poll,epoll 额外提供了边缘触发模式
- 水平触发(LT):只要套接字关联的内核缓冲区有数据可读写,就会返回
- 边缘触发(ET):仅当套接字关联的内核缓冲区有新的数据到达,才会返回
目前大家普遍认为 ET 模式要比 LT 模式更高效,原因在于 ET 能减少系统调用的次数,仅当新数据到达时才返回。在使用 ET 时,我们必须确保每次 epoll_wait 返回后,都将内核缓冲区中的数据读取干净,避免数据丢失。
我们考虑如下问题来思考 ET 是否真的比 LT 更高效!
问题:如果 LT 模式下,我们确保每次 epoll_wait 返回时都将内核缓冲区中的数据读取干净,那么 LT 模式所需要的系统调用次数不是和 ET 一样多吗(仅在有新数据到达时才返回)?
答案:是的,在这种情况下 LT 和 ET 的效率是差不多的,但是 LT 在编程时,需要在写事件完成后移除对写事件的检测(否则 epoll_wait 会一直报可写),相对麻烦
接下来分情况讨论
- 内核缓冲区数据小于用户缓存区大小
- LT + 阻塞 I/O:一次 read
- LT + 非阻塞 I/O:一次 read
- ET + 阻塞 I/O:一次 read
- ET + 非阻塞 I/O:一次 read
- 内核缓冲区数据大于用户缓存区大小
- LT + 阻塞 I/O:一次 read,然后 epoll_wait 又立即返回,继续 read,循环往复至读完内核缓冲区数据
- LT + 非阻塞 I/O:读事件与 ET + 非阻塞 I/O 是一样的,但是对于写事件,将数据写完后,必须及时移除对写事件的检测,否则,每次 epoll_wait 都会返回(可写)
- ET + 阻塞 I/O:while(true) 中反复调用 read,但读完后无法主动跳出循环(read 读不到数据,被阻塞),就行卡住了一样
- ET + 非阻塞 I/O:while(true) 中反复调用 read,如果返回 -1 且 errno 为 EAGAIN 或 EWOULDBLOCK 则代表读完,可跳出循环
总结:
- epoll 边缘触发必须搭配非阻塞 I/O 的原因在于,ET 模式要求必须将内核缓冲区数读完,而阻塞 I/O 在读完后无法跳出循环
- 性能差异不在于 ET 或 LT,而在于阻塞 I/O 和非阻塞 I/O,只要保证使用非阻塞 I/O,且每次都将内核缓冲区数据读干净,那么二者性能就是一样的(epoll_wait 系统调用次数一致)。只不过 LT 在编程实现上需要对写事件特殊处理,显得比较鸡肋罢了。
1084

被折叠的 条评论
为什么被折叠?



