epoll惊群原因分析

摘要:
从内核代码分析,原因如下:epoll正在被调用。当等待时,设置epoll的等待队列回调函数为default_ wake_ function,在添加队列时调用该函数局部变量curr的值可以通过epoll_获得wait()的源代码,具体来说:curr-˃flags:WQ_FLAG_EXCLUSIVEcurr-˃func:default_wake_functiondefault_wake函数调用try_to_wake_up当一个等待epoll实例的进程被唤醒时,它将最终进入ep_scan_ready_在list()函数中,ep_scan-ready_list()将在回调模式下调用ep send_events_Proc(),将数据复制到用户空间。当ep_scan_ready_list()函数返回之前,它将再次确定epoll就绪链接列表rdllist是否为空。如果没有,它将唤醒其他进程!
考虑如下情况(实际一般不会做,这里只是举个例子):
  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惊群原因分析》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇java service wrapper 级别为info导致内存剧增直至溢出linux下性能测试工具netperf使用下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

C#Win32API编程之PostMessage

  由于C#屏蔽了很多操作系统内核级的操作,将保护机制进行了加强,通过普通方法是无法完成如后台键鼠模拟、进程内存读写、网络封包拦截等操作的。   而C#又提供了调用非托管代码的DllImport,使得我们可以调用操作系统较为底层的API来完善程序功能。   本文就C#调用Win32API函数PostMessage完成指定窗体后台键鼠模拟作为示例,粗略讲解一...

基于JVisualVM的可视化监控

监控本地的java进程 本小节我们介绍一下如何使用JDK自带的jvisualvm工具来监控本地的Java进程,该工具是一个图形化的监控工具。 jvisualvm官方文档地址如下:https://visualvm.github.io/documentation.html 介绍 VisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已...

阻塞队列和死锁

BlockingQueue        BlockingQueue是并发容器的一种,在J.U.C的包路径下,是线程安全的一种实现,是基于阻塞队列的,该接口提供了相对于Queue的新的put()和take()操作。put()添加元素时,当阻塞队列满的情况下会阻塞下来,当有空间时才能进行添加操作,添加到队列尾部;take()删除元素时,当队列为空时,也会阻塞...

JMeter学习(一)工具简单介绍

一、JMeter介绍 Apache JMeter是100%纯JAVA桌面应用程序,被设计为用于测试客户端/服务端结构的软件(例如web应用程序)。它可以用来测试静态和动态资源的性能,例如:静态文件,Java Servlet,CGI Scripts,Java Object,数据库和FTP服务器等等。JMeter可用于模拟大量负载来测试一台服务器,网络或者对象...

条件变量用例--解锁与signal的顺序问题

         我们知道,当调用signal/broadcast唤醒等待条件变量的其他线程时,既可以在加锁的情况下调用signal/broadcast,也可以在解锁的情况下调用。          那么,到底哪种情况更好呢?man手册中其实已经给出了答案:          The pthread_cond_broadcast() or pthread_...

Java NIO 学习笔记(七)----NIO/IO 的对比和总结

目录:Java NIO 学习笔记(一)----概述,Channel/BufferJava NIO 学习笔记(二)----聚集和分散,通道到通道Java NIO 学习笔记(三)----SelectorJava NIO 学习笔记(四)----文件通道和网络通道Java NIO 学习笔记(五)----路径、文件和管道 Path/Files/PipeJava NI...