前言
epoll 是Linux I/O 多路复用接口 select/poll 的改进,epoll也是实现I/O多路复用的一种方法。epoll的工作模式有水平触发(level trigger,LT,默认工作模式)与边缘触发(edge trigger,ET)两种。使用脉冲信号来解释LT和ET可能更加贴切。Level是指信号只需要处于水平,就一直会触发;而edge则是指信号为上升沿或者下降沿时触发。
LT
LT:Level-trigger模式,通俗点来说只要内核缓冲区有数据就一直通知,只要socket处于可读状态或可写状态,就会一直返回sockfd,即只要某个socket处于readable/writable状态,无论什么时候进行epoll_wait都会返回该socket
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你 的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表
ET
ET:Edge-trigger模式,只有状态发生变化才通知,只有当socket由不可写到可写或由不可读到可读,才会返回其sockfd,也就是只有某个socket从unreadable变为readable或从unwritable变为writable时,epoll_wait才会返回该socket。
ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述 符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。
其他IO模型的触发模式
select(),poll()模型都是水平触发模式,信号驱动IO是边缘触发模式,epoll()模型既支持水平触发,也支持边缘触发,默认是水平触发 。
LT与ET的区别
水平触发
-
对于读操作 只要缓冲内容不为空,LT模式返回读就绪。
-
对于写操作 只要缓冲区还不满,LT模式会返回写就绪。
边缘触发
- 对于读操作
- 当缓冲区由不可读变为可读的时候,即缓冲区由空变为不空的时候。
- 当有新数据到达时,即缓冲区中的待读数据变多的时候。
- 当缓冲区有数据可读,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLIN事件时。
- 对于写操作
- 当缓冲区由不可写变为可写时。
- 当有旧数据被发送走,即缓冲区中的内容变少的时候。
- 当缓冲区有空间可写,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLOUT事件时。
对比下的优缺点
对于LT
优点:编程更符合用户直觉,业务层逻辑更简单。 缺点:效率比ET低。
ET比LT更高效的原因在于:
ET在通知用户后,就会把fd从就绪队列里删除。而LT通知用户后fd还在就绪链表中,随着fd的增多,就绪链表越大。下次epoll要通知用户时还需要遍历整个就绪链表。遍历的性能是线性,如果fd的数量非常多,就会带来比较显著的效率下降。同样数量的fd下,LT模式维护的就绪链表比ET的大。
应用
- 对于监听的sockfd,最好使用水平触发模式,边沿触发模式会导致高并发情况下,有的客户端会连接不上,如果非要使用边沿触发,网上有的方案是用while来循环accept().
- 对于读写的connfd,水平触发模式下,阻塞IO和非阻塞IO效果都一样,不过为了防止特殊情况,还是建议设置阻塞。
- 对于读写的connfd,边沿触发模式下,必须使用非阻塞IO,并要一次性全部读写完数据。
ET常用于nginx
LT常用于redis