惊群(thundering herd)

多个进程/线程同时阻塞等同一个事件时,事件发生后,唤醒所有的进程,但最终只可能有一个进程/线程对该事件进行处理,其他在失败后重新休眠,这种性能浪费就是惊群

accept 惊群

主进程创建socket, bind, listen后,fork出多个子进程都循环处理(accept)这个socket

每个进程都阻塞在accpet上,当一个新的连接到来时,所有的进程都会被唤醒,但其中只有一个进程会accept成功,其余皆失败,重新休眠。这就是accept惊群

fork出多个进程是为了利用多核CPU

内核早解决这问题了

多个进程/线程都阻塞在对同一个 socket 的 accept 调用上时,新连接到来,内核只唤醒一个进程,其他进程保持休眠,压根就不会被唤醒

epoll惊群

accept 已无惊群问题,但 epoll 还有

即,如果多个进程/线程阻塞在监听同一个 listening socket fd 的 epoll_wait 上,当有一个新的连接到来时,所有的进程都会被唤醒。

考虑如下场景:

主进程创建 socket, bind, listen 后,将该 socket 加入到 epoll 中,然后 fork 出多个子进程,每个进程都阻塞在 epoll_wait 上,如果有事件到来,则判断该事件是否是该 socket 上的事件,如果是,说明有新的连接到来了,则进行 accept 操作。为了简化处理,忽略后续的读写以及对 accept 返回的新的套接字的处理,直接断开连接。

那么,当新的连接到来时,是否每个阻塞在 epoll_wait 上的进程都会被唤醒呢?

很多博客中提到,测试表明虽然 epoll_wait 不会像 accept 那样只唤醒一个进程/线程,但也不会把所有的进程/线程都唤醒。例如这篇文章:关于多进程 epoll 与 “惊群”问题。

为什么内核不处理 epoll 惊群

accept 应该只能被一个进程调用成功,内核很清楚这一点

但 epoll 监听的fd,除可能被 accept 调用外,还有可能是其他网络 IO 事件的

其他 IO 事件是否只能由一个进程处理(如一个文件会由多个进程来读写),得用户决定,内核不能强制

所以,对 epoll 的惊群,内核则不予处理