Hyiki's 技术博客 Back-end Dev Engineer

IO模型-水平触发与边缘触发

2020-03-31

前言

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的区别

水平触发

  1. 对于读操作 只要缓冲内容不为空,LT模式返回读就绪。

  2. 对于写操作 只要缓冲区还不满,LT模式会返回写就绪。

边缘触发

  1. 对于读操作
    1. 当缓冲区由不可读变为可读的时候,即缓冲区由空变为不空的时候。
    2. 当有新数据到达时,即缓冲区中的待读数据变多的时候。
    3. 当缓冲区有数据可读,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLIN事件时。
  2. 对于写操作
    1. 当缓冲区由不可写变为可写时。
    2. 当有旧数据被发送走,即缓冲区中的内容变少的时候。
    3. 当缓冲区有空间可写,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLOUT事件时。

对比下的优缺点

对于LT

优点:编程更符合用户直觉,业务层逻辑更简单。 缺点:效率比ET低。

ET比LT更高效的原因在于:

ET在通知用户后,就会把fd从就绪队列里删除。而LT通知用户后fd还在就绪链表中,随着fd的增多,就绪链表越大。下次epoll要通知用户时还需要遍历整个就绪链表。遍历的性能是线性,如果fd的数量非常多,就会带来比较显著的效率下降。同样数量的fd下,LT模式维护的就绪链表比ET的大。

应用

  1. 对于监听的sockfd,最好使用水平触发模式,边沿触发模式会导致高并发情况下,有的客户端会连接不上,如果非要使用边沿触发,网上有的方案是用while来循环accept().
  2. 对于读写的connfd,水平触发模式下,阻塞IO和非阻塞IO效果都一样,不过为了防止特殊情况,还是建议设置阻塞。
  3. 对于读写的connfd,边沿触发模式下,必须使用非阻塞IO,并要一次性全部读写完数据。

ET常用于nginx

LT常用于redis


下一篇 404 GrayDay

Comments

Content