论epoll的使用

来源:互联网 发布:mac窗口关闭快捷键 编辑:程序博客网 时间:2024/06/05 05:53
epoll的事件触发模式有默认的 level-trigger 模式和通过 EPOLLET 启用的 edge-trigger 模式两种。从 epoll 发展历史来看,它刚诞生时只有 edge-trigger 模式,后来因容易产生 race-cond 且不易被开发者理解,又增加了 level-trigger 模式并作为默认处理方式。二者的差异在于 level-trigger 模式下只要某个 fd 处于 readable/writable 状态,无论什么时候进行 epoll_wait 都会返回该 fd;而 edge-trigger 模式下只有某个 fd 从unreadable 变为 readable 或从 unwritable 变为 writable 时,epoll_wait 才会返回该 fd。

epoll默认的LT(level-trigger )模式。使用LT意味着 只要 fd 处于 readable/writable 状态,每次 epoll_wait 时都会返回该 fd,系统开销不说,自己处理时每次都要把这些fd轮询一遍,如果fd很多的话,不管这些fd有没有事件发生,epoll_wait 都会触发这些fd的轮询判断。常用的事件处理库很多都选择了 LT 模式,包括大家熟知的libevent和boost::asio等,为什么选择LT呢?那就不得不从ET的弊端说起。
epoll的高速模式,也就是ET(edge-trigger)模式。ET模式下,当有事件发生时,系统只会通知你一次,也就是调用epoll_wait 返回fd后,不管事件你处理与否,或者处理完全与否,再调用epoll_wait 时,都不会再返回该fd,这样programmer要自己保证在事件发生时及时有效的处理完。比如此时fd发生了EPOLLIN事件,在调用epoll_wait 后发现此事件,programmer要保证在本次轮询中对此fd进行了读操作,并且还要循环调用recv操作,一直读到recv的返回值小于请求值,或者遇到EAGAIN错误,不然下次轮询时,如果此fd没有再次触发事件,你就没有机会知道 这个fd需要你的处理。这样无形中就增加了programmer的负担和出错的机会。
LT模式下,无论此fd是否有事件发生,或者有事件未处理完,每次epoll_wait 时总会得到此fd供你处理。LT模式下带来的逻辑处理的方便性和不易出错性,让我们有理由把它作为首选。这可能也是为什么epoll后来在ET的基础上又增加了LT,并且将其作为默认模式的原因吧。

需要注意的是写事件:
a. 对于et来说,应用层向tcp缓冲区写,有可能应用层数据写完了,但是tcp缓冲没有写到EAGAIN事件,那么此时需要在应用层做个标记,表明tcp缓冲区是可写的,否则,由于et是只触发一次,应用层就再也不会被通知缓冲区可写了。
b. 对于lt来说,应用层确实会每次通知可写事件,问题在于,如果应用层没数据需要往Tcp缓冲区写的话,epoll还是会不停的通知你可写,这时候需要把描述符移出epoll,避免多次无效的通知。

ET模式在网络层方面的效率比LT要高,主要表现在:
1、网络IO比较小时,send buffer表现为一直可写,如果网络主循环没有延时操作的话,epoll_wait每次调用都会马上有事件返回,导致不必要的CPU空耗,如果加入延时处理,对于一些实时性要求比较高的操作会受到影响,必须耗费额外的逻辑处理。
2、在网络IO比较大,尤其是连接数比较多的时候,每次epoll_wait调用时LT模式肯定比ET模式多,因为之后需要对ready list 进行遍历处理,如果处理逻辑比较复杂,或者之前反馈的事件数LT比ET多很多的话,这时候效率差异就比较明显了。ET模式在网络主循环处理的效率肯定比LT模式要高,至于高多少,视具体应用和具体实现而定。
当然ET模式的代价就是增加了网络层的逻辑处理复杂度,必须手动维护fd当前的状态,在数据发送时也不能像LT模式那样直接丢到fd应用层的buffer中。

ET模式比LT模式在处理网络IO的时候,ET效率高,这个大家已经没有太多争论了。 而ET模式换来的代价,就是需要循环读取。 而写事件的监测,在我们项目中也是采用的标记法。 我们是按帧发送网络数据的。当发送数据时发现标志可写,就将写缓冲区中的数据写向SOCKET。 可写标记仅当写的时候发现不可写时,设置为false,而由epoll_wait触发变为true。除此没有其它改变这个标志的地方。  
LT是可行的,这个肯定毋庸置疑。并且LT后于ET模式出现,也是有其客观存在的意义的。 但ET模式也有很多人在用,特别是一些追求效率的人。 


原文链接:点击打开链接


1 0
原创粉丝点击