考虑如下情况(实际一般不会做,这里只是举个例子):
  1. 在主线程中创建一个socket、绑定到本地端口并监听
  2. 在主线程中创建一个epoll实例(epoll_create(2))
  3. 将监听socket添加到epoll中(epoll_ctl(2))
  4. 创建多个子线程,每个子线程都共享步骤2里创建的同一个epoll文件描述符,然后调用epoll_wait(2)等待事件到来accept(2)
  5. 请求到来,新连接建立

这里的问题就是,在第5步的时候,会有多少个线程被唤醒而从epoll_wait()调用返回?答案是不一定,可能只有一个,也可能有部分,也可能是全部。当然在多个线程都唤醒的情况下,只会有一个线程accept()调用会成功。

为何如此?从内核代码分析,原因如下:

在调用epoll_wait(2)的时候,设置的epoll的等待队列回调函数是default_wake_function,添加队列的时候调用的是__add_wait_queue_exclusive()。
ep_poll_callback()中唤醒操作调用的是wake_up_locked(&ep->wq),最终会调用__wake_up_common,后者会判断exclusive标志:
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, int wake_flags, void *key)
{
wait_queue_t *curr, *next; list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
unsigned flags = curr->flags; if (curr->func(curr, mode, wake_flags, key) &&
(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}

因为__wake_up_common()的调用是从wake_up_locked()开始的,__wake_up_common的各个参数值为:

  • q: struct eventpoll.wq
  • mode: TASK_NORMAL
  • nr_exclusive:1
  • wake_flags: 0
  • key:NULL。
局部变量curr的值可以通过epoll_wait()的源码得到,具体为:
  • curr->flags: WQ_FLAG_EXCLUSIVE
  • curr->func: default_wake_function
default_wake_function调用的是try_to_wake_up。而try_to_wake_up只有在要唤醒的进程状态不是TASK_NORMAL时才会返回0,TASK_NORMAL的定义是(TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)。
因此__wake_up_common里的if条件会在第一次判断的时候就满足,唤醒一个进程后便返回了,那为什么实际测试会发现有多个进程被唤醒呢?
原因就在于这个唯一被唤醒的进程。
当某个等待在epoll实例上的进程被唤醒后,最终会进入到ep_scan_ready_list() 这个函数中,ep_scan_ready_list()会以回调方式调用ep_send_events_proc()来将数据复制到用户空间。而ep_scan_ready_list()函数在返回之前会再次判断epoll的就绪链表rdllist是否为空,如果不为空的话,就会再唤醒其他进程!下面就是ep_scan_ready_list()返回之前的判断操作:
	if (!list_empty(&ep->rdllist)) {
/*
* Wake up (if active) both the eventpoll wait list and
* the ->poll() wait list (delayed after we release the lock).
*/
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq);
if (waitqueue_active(&ep->poll_wait))
pwake++;
}
而在水平触发方式下,从就绪链表中移出来的文件描述符,如果当前仍有事件就绪(可读、可写等),会在复制到用户空间后被再次添加到就绪链表中:
if (epi->event.events & EPOLLONESHOT)
epi->event.events &= EP_PRIVATE_BITS;
else if (!(epi->event.events & EPOLLET)) {
/*
* If this file has been added with Level
* Trigger mode, we need to insert back inside
* the ready list, so that the next call to
* epoll_wait() will check again the events
* availability. At this point, no one can insert
* into ep->rdllist besides us. The epoll_ctl()
* callers are locked out by
* ep_scan_ready_list() holding "mtx" and the
* poll callback will queue them in ep->ovflist.
*/
list_add_tail(&epi->rdllink, &ep->rdllist);
ep_pm_stay_awake(epi);
}
因此在水平触发模式下,被唤醒的进程又会去唤醒其他进程,除非当前事件已经被处理完或者所有进程都已经被唤醒(被唤醒的进程会从epoll等待队列上移除)。
 

epoll惊群原因分析的更多相关文章

  1. accept与epoll惊群 转载

    今天打开 OneNote,发现里面躺着一篇很久以前写的笔记,现在将它贴出来. 1. 什么叫惊群现象 首先,我们看看维基百科对惊群的定义: The thundering herd problem occ ...

  2. epoll 惊群处理

    #include <sys/types.h> #include <sys/socket.h> #include <sys/epoll.h> #include < ...

  3. 源码剖析Linux epoll实现机制及Linux上惊群

    转载:https://blog.csdn.net/tgxallen/article/details/78086360 看源码是对一个技术认识最直接且最有效的方式了,之前用Linux Epoll做过一个 ...

  4. nginx&http 第三章 惊群

    惊群:概念就不解释了. 直接说正题:惊群问题一般出现在那些web服务器上,Linux系统有个经典的accept惊群问题,这个问题现在已经在内核曾经得以解决,具体来讲就是当有新的连接进入到accept队 ...

  5. Linux惊群效应详解

    Linux惊群效应详解(最详细的了吧)   linux惊群效应 详细的介绍什么是惊群,惊群在线程和进程中的具体表现,惊群的系统消耗和惊群的处理方法. 1.惊群效应是什么?        惊群效应也有人 ...

  6. Spark集群无法停止的原因分析和解决

    今天想停止spark集群,发现执行stop-all.sh的时候spark的相关进程都无法停止.提示: no org.apache.spark.deploy.master.Master to stop ...

  7. NGINX怎样处理惊群的

    写在前面 写NGINX系列的随笔,一来总结学到的东西,二来记录下疑惑的地方,在接下来的学习过程中去解决疑惑. 也希望同样对NGINX感兴趣的朋友能够解答我的疑惑,或者共同探讨研究. 整个NGINX系列 ...

  8. Linux网络编程“惊群”问题总结

    1.前言 我从事Linux系统下网络开发将近4年了,经常还是遇到一些问题,只是知其然而不知其所以然,有时候和其他人交流,搞得非常尴尬.如今计算机都是多核了,网络编程框架也逐步丰富多了,我所知道的有多进 ...

  9. epoll(2) 源码分析

    epoll(2) 源码分析 文本内核代码取自 5.0.18 版本,和上一篇文章中的版本不同是因为另一个电脑出了问题,但是总体差异不大. 引子留下的问题 关键数据结构 提供的系统调用 就绪事件相关逻辑 ...

随机推荐

  1. 利用excel办公软件快速拼凑sql语句

    日常工作中经常会收到excel整理好的部门或者人员等数据信息并需要批量更新或者插入到数据库中,常用的办法有导入.脚本拼凑执行等,今天我介绍直接使用excel快速拼凑sql语句的方法 1.update批 ...

  2. OCM_第十七天课程:Section7 —》GI 及 ASM 安装配置 _管理和配置 GRID /实施 ASM 故障组 /创建 ACFS 文件系统

    注:本文为原著(其内容来自 腾科教育培训课堂).阅读本文注意事项如下: 1:所有文章的转载请标注本文出处. 2:本文非本人不得用于商业用途.违者将承当相应法律责任. 3:该系列文章目录列表: 一:&l ...

  3. python之比较is与==(转载)

    在 Python 中会用到对象之间比较,可以用 ==,也可以用 is .但是它们的区别是什么呢? is 比较的是两个实例对象是不是完全相同,它们是不是同一个对象,占用的内存地址是否相同.莱布尼茨说过: ...

  4. passive 的事件监听器(转载)

    passive 的事件监听器 很久以前,addEventListener() 的参数约定是这样的: addEventListener(type, listener, useCapture) 后来,最后 ...

  5. 温故而知新--JavaScript书摘(二)

    前言 毕业到入职腾讯已经差不多一年的时光了,接触了很多项目,也积累了很多实践经验,在处理问题的方式方法上有很大的提升.随着时间的增加,愈加发现基础知识的重要性,很多开发过程中遇到的问题都是由最基础的知 ...

  6. P3660 【[USACO17FEB]Why Did the Cow Cross the Road III G】

    题外话:维护区间交集子集的小套路 开两个树状数组,一个维护进入区间,一个维护退出区间 $Query:$ 给定询问区间$l,r$和一些其他区间,求其他区间中与$[l,r]$交集非空的区间个数 用上面维护 ...

  7. zoj3195 联通树上三个点的路径长

    输出有个坑,两个月之前就没对,,今天又被坑了一次 求联通树上三个点的路径长度,只要求两两点对的最短路径,加起来除以二即可 #include<iostream> #include<cs ...

  8. bootstrap之表单和图片

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. 【Algorithm | 链表】单链表“环”、“环的起点”、“环的长度”问题

    参考资料 • Floyd判圈算法 { 链接 } • 单链表“环”.“环的起点”.环的长度”问题 { 链接 } 链表环的问题 一.判断链表有换 使用两个指针slow和fast.两个指针开始时均在头节点处 ...

  10. poj 1631 最多能有多少条不交叉的线 最大非降子序列 (LIS)

    左边的数字是1 2 3 4 5.... 右边的数字 第一个输入的和1连 第2个输入的和2连 右边再按从小到大排序 要求连线不能交叉 问最多能有多少条不交叉的线 假如右边有5个1 那么答案会是5 所以是 ...