前几天回答一个问题,是关于我们项目中使用的epoll模式的,因为记不大清了,感觉应该使用的就是epoll的高速模式,也就是ET(edge-trigger)模式。这两天闲暇的时候,打开代码又看了一下,在epoll事件注册时并未标记ET模式,看来实际使用的是epoll默认的LT(level-trigger )模式,为什么呢?使用LT意味着 只要 fd 处于 readable/writable 状态,每次 epoll_wait 时都会返回该 fd,系统开销不说,自己处理时每次都要把这些fd轮询一遍,如果fd很多的话,不管这些fd有没有事件发生,epoll_wait 都会触发这些fd的轮询判断。
查阅了一些资料,才知道常用的事件处理库很多都选择了 LT 模式,包括大家熟知的libevent和boost::asio等,为什么选择LT呢?那就不得不从ET的弊端的弊端说起。
ET模式下,当有事件发生时,系统只会通知你一次,也就是调用epoll_wait 返回fd后,不管事件你处理与否,或者处理完全与否,再调用epoll_wait 时,都不会再返回该fd,这样programmer要自己保证在事件发生时及时有效的处理完。比如此时fd发生了EPOLLIN事件,在调用epoll_wait 后发现此事件,programmer要保证在本次轮询中对此fd进行了读操作,并且还要循环调用recv操作,一直读到recv的返回值小于请求值,或者遇到EAGAIN错误,不然下次轮询时,如果此fd没有再次触发事件,你就没有机会知道这个fd需要你的处理。这样无形中就增加了programmer的负担和出错的机会。
ET模式的短处正是LT模式的长处,无论此fd是否有事件发生,或者有事件未处理完,每次epoll_wait 时总会得到此fd供你处理。显而易见,OS在LT模式下维护的 ready list 的大小肯定比ET模式下长,而且你自己轮询所有的fd时也要比ET下要多,这种消耗和ET模式下循环调用处理函数(如recv和send等),还要逻辑处理是否处理完毕,理论上应该是LT更大一些,不过个人感觉应该差别不会太大。但是LT模式下带来的逻辑处理的方便性和不易出错性,让我们有理由把它作为首选。我想这可能也是为什么epoll后来在ET的基础上又增加了LT,并且将其作为默认模式的原因吧。
peakflys 上述观点,欢迎 志同道合或志同道不合的朋友拍砖。
PS:文中一味写LT的好处,没有说LT 极易引起的写触发 频繁通知的问题,具体大家可以参考评论部分,再次感谢大家的指教。