Chinaunix

标题: epoll 惊群相关 [打印本页]

作者: zylthinking    时间: 2013-05-09 18:40
标题: epoll 惊群相关
本帖最后由 zylthinking 于 2013-05-09 18:49 编辑

重温 epoll, 同时给之前研究 epoll 的几位看看, 上次读 epoll 还是因为那几个帖子。

epoll 现在仍然还是有惊群现象的, 但这不是大问题, 因为导致惊群出现的用法已经怎么看怎么不合理了: 如果你将同一个 fd 加入不同的 epollfd, 那么不管你怎么设置属性, 包括前段时间热炒的大招  EPOLLONESHOT, 都避免不了。 好的是, 谁也不会这么干。

epoll 实现基本数据结构有三个, eventpoll, epoll_item, epoll_entry
eventpoll 对应 epollfd
epoll_item 对应加入到某个 epoll 的 fd 实例, 如果同一个 fd 加入多个 epollfd, 则存在多个 epoll_item
epoll_entry 和 epoll_item 一一对应。

相互之间关系为, eventpoll 中保存 epoll_item 链表, epoll_item 和 epoll_entry 一一对应, 通过 epoll_entry 加入到 fd 对应的 file_struct 的 wait 队列。
一旦 fd 有事件了, 通过 wait_list 到达 epoll_entry 然后最终到达 eventpoll, 然后在 epoll_wait 的线程唤醒, 将事件写入用户空间。
这里有个 eventpoll 级别的锁,因此就算多个 epoll_wait 唤醒, 最终只有一个会得到事件, 剩下的空转一圈接着睡眠, 这就是 epoll_wait 为何不惊群的原因, 但要注意不惊群是针对同一个 eventpoll 而言的, 对不同 eventpoll, 是没办法的。
EPOLLONESHOT 是 epoll_item 级别的, 设置方是获得了事件的那个线程, 由于 epoll_item 局限于 eventpoll, 因此 EPOLLONESHOT 也不能阻止同时存在于另一个 epollfd 的 fd 上出现事件, 而导致惊群。
作者: chenzhanyiczy    时间: 2013-05-09 21:30
right。

"因此就算多个 epoll_wait 唤醒, 最终只有一个会得到事件, 剩下的空转一圈接着睡眠,"
-->这里应该不会有多个唤醒,我记得是唤醒单个
作者: zylthinking    时间: 2013-05-10 06:49
chenzhanyiczy 发表于 2013-05-09 21:30
right。

"因此就算多个 epoll_wait 唤醒, 最终只有一个会得到事件, 剩下的空转一圈接着睡眠,"


在内核中空转, 用户层怎么会知道
作者: linux_c_py_php    时间: 2013-05-10 10:06
就是说无论一个fd被几个线程几个epollset注册了, 并发的话内核里都避免不了空转, 但应用层看起来是防惊群的.
作者: zylthinking    时间: 2013-05-10 10:27
zylthinking 发表于 2013-05-10 06:49
在内核中空转, 用户层怎么会知道


错了, 没看仔细, 内核里面也不会空转, wake up 是 exclusive 的, 意味着只有对列中第一个线程被唤醒。
但也应该有并发竞争的情况, 出现于在唤醒的同时存在至少一个刚刚进入 epoll_wait 还没有来得及进入 wait list 睡眠, 这样这个刚刚进入的线程有可能会和被唤醒的线程同时竞争事件, 避免这个竞态的是 epollevent 里面的锁
作者: zylthinking    时间: 2013-05-10 10:31
本帖最后由 zylthinking 于 2013-05-10 10:41 编辑
linux_c_py_php 发表于 2013-05-10 10:06
就是说无论一个fd被几个线程几个epollset注册了, 并发的话内核里都避免不了空转, 但应用层看起来是防惊群的 ...


一个 fd 被多于一个 epollset 注册, 则无法防止应用层惊群。
唯一避免的办法是不同时注册于多于一个 epollset.
其实想想如果在实现时不使用一个 fd 可能对应多个 epitem 结构, 而是多个 epollevent 共享同一个 epitem, 则可以实现即便同时注册于多个 epollset 也不会产生应用层惊群。 不知到 epoll 为什么要这么实现, 或者有难以绕过去的困难吧, 看代码看到的理解毕竟浅薄。

上面提到的内核中可能存在的空转发生于对同一个 epollset 进行 wait 的多个线程; 但也不是常见, 看楼上
作者: linux_c_py_php    时间: 2013-05-10 11:18
噢噢,那就是和之前的认识一致,多个epoll肯定是惊群的,但一个epoll多个线程无论是否EPOLLONESHOT都可能会空转,但注册了EPOLLONESHOT后对应用层透明。

我之前也看了一下epoll的代码,不过只是想看看是不是有锁,另外看了一下EPOLLERR和EPOLLHUP是不是默认注册,发现只有在EPOLLONESHOT返回后这两个标记才能真正清楚,所以做leader-follower/half-sync-half-async都有必须用EPOLLONESHOT来清理标记。
作者: chenzhanyiczy    时间: 2013-05-10 14:21
本帖最后由 chenzhanyiczy 于 2013-05-10 14:30 编辑
zylthinking 发表于 2013-05-10 10:27
错了, 没看仔细, 内核里面也不会空转, wake up 是 exclusive 的, 意味着只有对列中第一个线程被唤醒 ...



特意查了一下。之前的说法有点出入。

在linux 2.6.18下,会发现惊群,因为加入等待队列的时候,没有设置exclusive。代码如下:
__add_wait_queue(&ep->wq, &wait);
而之后的版本(具体哪个版本开始就不知道)采用的是如下:
__add_wait_queue_exclusive(&ep->wq, &wait);
这样避免了惊群。

同时你后面说的那个情况,就不是惊群问题了。惊群的前提是:笼统地说,大家都在睡眠等待资源的可用
作者: yulihua49    时间: 2015-07-03 13:35
linux_c_py_php 发表于 2013-05-10 10:06
就是说无论一个fd被几个线程几个epollset注册了, 并发的话内核里都避免不了空转, 但应用层看起来是防惊群的 ...


http://bbs.chinaunix.net/thread-4181206-1-1.html
看日志,明显的惊群了。
Linux mtrlin 2.6.32-279.el6.x86_64 #1 SMP Fri Jun 22 12:19:21 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2