io多路复用的边缘触发模式和水平触发模式

io多路复用是内核同时监控多个文件描述符,当文件描述符上某事件(比如读和写)就绪,就会向调用进程发送通知的机制。io多路复用有两种触发通知的机制—边缘触发(Edge Triggered)和水平触发(Level Triggered)。这一篇文章就讲一讲这两种触发机制。

水平触发

如果文件描述符上可以非阻塞地执行I/O系统调用,此时认为它已经就绪,触发通知.
在网络服务器的开发中,假如我们以水平触发模式的io多路复用来同时监控多个已连接描述符,当某一描述符上有数据可读的时候,就会触发通知(一般来说,就是相应调用返回)。应用程序在收到通知后可以选择读取所有可读的数据,读取部分数据,甚至是不读取任何数据。对于读取部分数据和不读取任何数据这种处理方式中,当下一次检查的时候,该文件描述符还是被返回(尽管在这一段时间内,该文件描述符上没有任何新的时间发生),被返回的原因则是该文件描述符上仍然有数据可读,即仍然可以在其上非阻塞的执行read()。也就是说,在水平触发模式中,当前事件的消息不一定必须要当前读取完毕,留到之后读取也没有关系。

边缘触发

如果文件描述符自上次状态检查以来有了新的I/O活动(比如新的输入,用于写入的缓冲区腾出了空间等),此时需要触发通知。
仍然以网络服务器开发为例,这次我们使用边缘触发,当某一文件描述符上有数据可读,假如我们仍然是部分读取数据,那么在下一次检查的时候,我们将得不到该文件描述符已经ready的通知(除非在这一段时间内,该描述符上面有新的数据到来)。边缘触发只有在有新的i/o活动的时候才会触发通知,及时当前套接字上有数据,可以非阻塞的执行I/O系统调用,应用程序也得不到通知。也就是说,在边缘触发模式中,在一次通知中,我们应该尽可能的多的读取数据,直到不能再读为止。具体的做法是这样的:文件描述符应该提前设置问非阻塞(O_NONBLOCK),在已就绪文件描述符上循环的读取数据,知道read()返回-1,并且erronEAGAINEWOULDBLOCK.

在上面我们对水平触发和边缘触发的分析中,我们可以知道边缘触发相比于水平触发来讲,降低了同一个事件被重复触发的概率,减少了多路复用系统调用的次数,因此边缘触发的性能由于水平触发。

在我们常用的io多路复用系统调用中,selectpoll只支持水平触发,epoll则既支持水平触发也支持边缘触发。