考虑如下情况(实际一般不会做,这里只是举个例子):
  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. 单个 LINQ to Entities 查询中的两个结构上不兼容的初始化过程中出现类型“XXXX”

    最近在做一个报表的时候,用EF使用了Contact方法,但是程式运行一直出错.最近终于找到原因了,写下来提醒下自己.好了,进入正题: 现在我举个栗子,目前数据库中有ParentStudent表和Sub ...

  2. 【5分钟一个知识点】JS一文搞懂new操作符

    关于new操作符,看了两本书<Javascript高级程序设计3>和<你不知道的JS上>,以及其他文档后,终于豁然开朗. 现总结如下,希望同样懵逼的你,彻底理解它. 如果有不同 ...

  3. OCM_第八天课程:Section4 —》数据管理

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

  4. MySQL CPU 使用率高的原因和解决方法

    用户在使用 MySQL 实例时,会遇到 CPU 使用率过高甚至达到 100% 的情况.本文将介绍造成该状况的常见原因以及解决方法,并通过 CPU 使用率为 100% 的典型场景,来分析引起该状况的原因 ...

  5. 并行(多进程)-python

    1.进程创建 2.当前进程信息 使用current_process可获得当前进程的信息: (1)引入:from multiprocessing import current_process (2)获取 ...

  6. Mac下Eclipse读取不到环境变量

    问题: 用Eclipse时候读取不到 ~/.bash_profile 下定义的环境变量,确切的说,是GUI应用读取不到终端的(如eclipse) 解决: 1,下载启动代理器: curl https:/ ...

  7. js后退

    history.back(-1):直接返回当前页的上一页,数据全部消息,是个新页面 history.go(-1):也是返回当前页的上一页,不过表单里的数据全部还在 history.back(0) 刷新 ...

  8. 第三章XML简介

    概念:XML:提供数据交换.系统配置.内容管理等的功能,可跨平台.跨网络.跨程序的数据描述方式.XSL:依靠XPath定位,提供显示模板,且专门为了显示XML文件信息的语言.CSS(层叠样式表):在网 ...

  9. Quratz入门

    一:介绍 1.应用场景 基本上任何公司都会用到调度这个功能, 比如我们公司需要定期执行调度生成报表, 或者比如博客什么的定时更新之类的,都可以靠Quartz来完成.正如官网所说,小到独立应用大到大型电 ...

  10. Nginx禁止直接通过IP地址访问网站以及限制IP登陆某目录(关闭默认站点或空主机头)

    这篇文章主要介绍了Nginx中禁止使用IP访问网站的配置实例,一般在备案时可能需要这种设置,需要的朋友可以参考下   国内因为备案的原因,所有服务器都要禁止使用IP访问网站.否则,如果允许使用IP访问 ...