epoll的边缘触发选项有什么目的?

74

从 epoll 的 man 页面中:

epoll is a variant of poll(2) that can be used either as an edge-triggered
or a level-triggered interface

何时应使用边缘触发选项?手册提供了一个使用它的示例,但我不明白为什么在该示例中需要使用它。

3个回答

126

当一个FD(文件描述符)变得可读或可写时,并不一定立即想要读取(或写入)所有数据。

水平触发的 epoll(事件轮询机制)将在 FD 保持就绪的情况下一直提醒你,而边缘触发的 epoll 只有在下次接收到 EAGAIN(表示暂时无法读写)时才会再次提醒你(这样编码更加复杂,但根据需要执行的任务不同可能更加高效)。

比如你正在从资源向 FD 写入数据。如果你以水平触发的方式注册 FD 可写,那么你会不断收到通知,告诉你 FD 仍处于可写状态。如果该资源尚不可用,则这是一次唤醒的浪费,因为你无法继续写入任何内容。

如果改为以边缘触发的方式注册,你只会接收到一次通知,告诉你 FD 可写,然后在其他资源准备好后尽力写入尽可能多的数据。如果 write(2) 返回 EAGAIN,则停止写入并等待下次通知。

对于读取也是同样的道理,因为在准备进行特定操作之前,你可能不想将所有数据都拉入用户空间(这样需要进行缓冲等操作)。通过边缘触发的 epoll 机制,你会得到关于何时可以读取数据的通知,然后只需记录并在需要时实际读取。


5
这种边缘触发行为能否安全地避免竞态条件,例如如果在 read 返回 EAGAIN 之后但在调用 epoll 之前数据可用的情况下? - R.. GitHub STOP HELPING ICE
32
在多核机器上,使用多线程服务器使得ET表现尤为出色。你可以针对每个核心运行一个线程,并让它们都在同一个epfd上调用epoll_wait函数。当有数据到达某个fd时,只会唤醒其中一个线程来处理。 - Chris Dodd
5
请纠正我如果我错了,但在LT模式下,可能会有多个线程在同一FD/SD上同时被唤醒,只要有数据存在。使用ET模式时,当有数据时仅设置一个FD/SD的通知,因此只有一个线程会收到这样的通知;其他线程可能会收到相同FD/SD的通知,但仅在原始线程读取/写入全部数据以进行通知时才会收到;可以想象使用ET更容易编写多线程epoll进程。希望这能帮到您。 - Emanuele
10
@Emanuele - 确认,ET保证只有一个线程会被唤醒。 - Alex
1
@Emanuele 你可能是错误的。man 7 epoll: 即使使用边缘触发的 epoll,在接收到多个数据块时仍然可以生成多个事件,调用者可以选择指定 EPOLLONESHOT 标志,告诉 epoll 在使用 epoll_wait(2) 接收到事件后禁用相关联的文件描述符。当指定了 EPOLLONESHOT 标志时,调用者有责任使用 epoll_ctl(2)EPOLL_CTL_MOD 重新启用文件描述符。 - zeekvfu
显示剩余7条评论

10

在我的实验中,ET模式不能保证只会有一个线程被唤醒,尽管通常只会唤醒一个线程。EPOLLONESHOT标志就是为了解决这个问题。


5
由于即使使用边缘触发的 epoll,在接收多个数据块时可能会生成多个事件,调用者可以选择指定 EPOLLONESHOT 标志来告诉 epoll 在使用 epoll_wait(2) 接收到一个事件后禁用关联的文件描述符。当指定 EPOLLONESHOT 标志时,重新启用文件描述符的责任由调用者负责,需要使用 epoll_ctl(2) 函数以 EPOLL_CTL_MOD 为参数进行重新设置。 - zeekvfu
1
确切地说,每次上升沿只会通知您一次。如果将stdin添加到作为EPOLLET的epoll集合中,则每按一次回车键都会生成一个事件。这就是为什么需要EPOLLONESHOT的原因。 - Guido
你的 epoll FD 是不同的,还是在线程之间共享一个?我的理解是,所有的 epoll FD 都应该被唤醒,但对于共享的 FD,可能只有一个线程会被唤醒。新的 EPOLLEXCLUSIVE 可以解决多个 epoll FD 的惊群问题。 - Goswin von Brederlow
我的意思是多个线程正在等待单个FD。如果未设置EPOLLONESHOT标志,有时会唤醒几个线程。而如果设置了该标志,则只有一个线程会被唤醒。 - cpq

3
  • Level triggered

    当你无法在FD中消耗所有数据并希望epoll在数据可用时继续触发时,请使用水平触发模式。

    例如,如果您想从FD接收大型文件,并且一次无法从FD中消耗所有文件数据,并希望保持触发以进行下一次消耗,则水平触发模式可能适合此情况。

    • 缺点

      • 惊群现象
        • EPOLLEXCLUSIVE指令旨在防止惊群现象
      • 效率较低
        • 当监视的文件描述符上发生读/写事件时,epoll_wait()会通知处理程序进行读取或写入。如果您不能一次读取或写入所有数据(例如,读/写缓冲区太小),则下一次调用epoll_wait()时,它将通知您继续在您没有完成读取或写入的文件描述符上进行读取或写入,但是如果您从未读取或写入,它将继续通知您。
        • 如果系统有大量准备好的文件描述符,而您不需要读取或写入它们,并且它们每次都返回,这可以大大降低处理程序检索其关心的准备好的文件描述符的效率。
    • 使用情况

      • redis epoll由于Redis的IO线程是单线程的,因此使用水平触发模式。
  • Edge triggered

    使用边缘触发模式并确保所有可用数据都被缓冲并最终处理。

    Chris Dodd在评论中提到:

    对于多核机器上的多线程服务器,ET也非常好。您可以为每个核心运行一个线程,并让它们在同一FD上调用epoll_wait。当FD上有数据时,将唤醒一个线程来处理它


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接