从 epoll 的 man 页面中:
epoll is a variant of poll(2) that can be used either as an edge-triggered
or a level-triggered interface
何时应使用边缘触发选项?手册提供了一个使用它的示例,但我不明白为什么在该示例中需要使用它。
当一个FD(文件描述符)变得可读或可写时,并不一定立即想要读取(或写入)所有数据。
水平触发的 epoll(事件轮询机制)将在 FD 保持就绪的情况下一直提醒你,而边缘触发的 epoll 只有在下次接收到 EAGAIN(表示暂时无法读写)时才会再次提醒你(这样编码更加复杂,但根据需要执行的任务不同可能更加高效)。
比如你正在从资源向 FD 写入数据。如果你以水平触发的方式注册 FD 可写,那么你会不断收到通知,告诉你 FD 仍处于可写状态。如果该资源尚不可用,则这是一次唤醒的浪费,因为你无法继续写入任何内容。
如果改为以边缘触发的方式注册,你只会接收到一次通知,告诉你 FD 可写,然后在其他资源准备好后尽力写入尽可能多的数据。如果 write(2)
返回 EAGAIN
,则停止写入并等待下次通知。
对于读取也是同样的道理,因为在准备进行特定操作之前,你可能不想将所有数据都拉入用户空间(这样需要进行缓冲等操作)。通过边缘触发的 epoll 机制,你会得到关于何时可以读取数据的通知,然后只需记录并在需要时实际读取。
在我的实验中,ET模式不能保证只会有一个线程被唤醒,尽管通常只会唤醒一个线程。EPOLLONESHOT标志就是为了解决这个问题。
epoll
,在接收多个数据块时可能会生成多个事件,调用者可以选择指定 EPOLLONESHOT
标志来告诉 epoll
在使用 epoll_wait(2)
接收到一个事件后禁用关联的文件描述符。当指定 EPOLLONESHOT
标志时,重新启用文件描述符的责任由调用者负责,需要使用 epoll_ctl(2)
函数以 EPOLL_CTL_MOD
为参数进行重新设置。 - zeekvfuEPOLLEXCLUSIVE
可以解决多个 epoll FD 的惊群问题。 - Goswin von BrederlowLevel triggered
当你无法在FD中消耗所有数据并希望epoll在数据可用时继续触发时,请使用水平触发模式。
例如,如果您想从FD接收大型文件,并且一次无法从FD中消耗所有文件数据,并希望保持触发以进行下一次消耗,则水平触发模式可能适合此情况。
缺点
EPOLLEXCLUSIVE
指令旨在防止惊群现象epoll_wait()
会通知处理程序进行读取或写入。如果您不能一次读取或写入所有数据(例如,读/写缓冲区太小),则下一次调用epoll_wait()时,它将通知您继续在您没有完成读取或写入的文件描述符上进行读取或写入,但是如果您从未读取或写入,它将继续通知您。使用情况
Edge triggered
使用边缘触发模式并确保所有可用数据都被缓冲并最终处理。
如Chris Dodd在评论中提到:
对于多核机器上的多线程服务器,ET也非常好。您可以为每个核心运行一个线程,并让它们在同一FD上调用epoll_wait。当FD上有数据时,将唤醒一个线程来处理它
read
返回EAGAIN
之后但在调用epoll
之前数据可用的情况下? - R.. GitHub STOP HELPING ICEman 7 epoll
: 即使使用边缘触发的epoll
,在接收到多个数据块时仍然可以生成多个事件,调用者可以选择指定EPOLLONESHOT
标志,告诉epoll
在使用epoll_wait(2)
接收到事件后禁用相关联的文件描述符。当指定了EPOLLONESHOT
标志时,调用者有责任使用epoll_ctl(2)
和EPOLL_CTL_MOD
重新启用文件描述符。 - zeekvfu