epoll模型实现及惊群效应

惊群效应:高并发编程的隐形杀手 惊群效应是多个进程/线程同时监听同一资源时,事件触发导致所有等待者被无效唤醒的现象,造成CPU资源浪费和系统延迟。主要发生在Socket监听、I/O多路复用等场景。解决方案包括:内核层面采用SO_REUSEPORT选项实现精准唤醒;应用层通过主从进程模型或互斥锁隔离监听;或直接选用已优化该问题的框架(如Nginx、Netty)。检测方法包括监控CPU使用率、跟踪系统调用等。建议优先使用内核级优化或成熟框架,在高并发场景下平衡性能与兼容性。 阅读详情

        笔者之前写过关于epoll的两篇博文:浅谈epoll的水平触发与边沿触发_码农诗人的博客-优快云博客和Nginx事件模块学习之epoll_码农诗人的博客-优快云博客仅仅只是对epoll模型的基本用法做一记录学习。本篇主要对epoll模型的代码实现及epoll的惊群效应做以验证加深理解。

概念

       

         如上图,服务器端程序往往为了服务端的并发量及处理数据强度增多而选择多进程或多线程。从而出现上述的实现结构:主进程负责创建监听socket并绑定、监听程序,然后创建多个子进程去accept监听socket上的连接事件。这样在有一个客户端连接到来时,同时会惊醒所有在accept监听的子进程,这种现象被称为惊群现象。

案例

        案例代码如下:

/****************************************
	author : lijd
	date   : 2022-09-09
****************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define true 				1
#define false				0
#define MAX_EVENT_NUMBER 	1024
#define BUFFER_SIZE 		10

#define PROCESS_NUM			3

/*将文件描述符设置为非阻塞*/
int setnonblocking(int fd)
{
	int old_option = fcntl(fd, F_GETFL);
	int new_option = old_option | O_NONBLOCK;
	fcntl(fd, F_SETFL, new_option);
	return old_option;
}

/*将文件描述符fd上的EPOLLIN注册到epollfd指示的epoll内核事件表中。 参数enable_et 指定是否对fd采用ET模式*/
void addfd(int epollfd, int fd, int enable_et)
{
	struct epoll_event event;
	event.data.fd = fd;
	event.events  = EPOLLIN;
	if(enable_et){
		event.events |= EPOLLET;
	}
	epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event );
	setnonblocking(fd);
}
/*LT 模式的工作原理*/
void lt(struct epoll_event *events, int number, int epollfd, int listenfd)
{
	char buf[BUFFER_SIZE];
	int i = 0;
	for(i= 0;i<number;i++){
		int sockfd = events[i].data.fd;
		if(sockfd == listenfd){
			struct sockaddr_in client_address;
			socklen_t client_addrlength = sizeof(client_address);
			int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlength);
			addfd(epollfd, connfd, false);
		}else if(events[i].events & EPOLLIN){
			/*只要socket读缓存中的还有未读的数据,此代码就能触发*/
			printf("event trigger once\n");
			memset(buf, 0, BUFFER_SIZE);
			int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
			if(ret <= 0){
				close(sockfd);
				continue;
			}
			printf("get %d bytes of content: %s\n", ret, buf);
		}else{
			printf("Something else happen !!!\n");
		}
	}
}

/*ET 模式的工作流程*/
void et(struct epoll_event* events, int number, int epollfd, int listenfd)
{
	char buf[BUFFER_SIZE];
	int i=0;
	for(i =0; i<number; i++){
		int sockfd = events[i].data.fd;
		if(sockfd == listenfd){
			struct sockaddr_in client_address;
			socklen_t client_addrlength = sizeof(client_address);
			int connfd = accept(listenfd, (struct sockaddr* )&client_address, &client_addrlength);
			
			addfd(epollfd, connfd, true);/*对connfd开启ET模式*/
		}else if(events[i].events & EPOLLIN){
			/*这段代码不会被重复触发,所以我们循环读取数据,以确保把socket缓冲区的数据全部读取*/
			printf("Event trigger once !!!\n");
			while(1){
				memset(buf, 0, BUFFER_SIZE);
				int ret  = recv(sockfd, buf, BUFFER_SIZE-1, 0);
				if(ret < 0){
					/*对于非阻塞IO,下面的事件成立标识数据已经全部读取完毕。此后,epoll就能再次触发sockfd上的sockfd的EPOLLIN事件,以驱动下一次读操作*/
					if((errno == EAGAIN) || (errno == EWOULDBLOCK)){
						printf("read later\n");
						break;
					}
					close(sockfd);
					break;
				}else if(ret == 0){
					close(sockfd);
				}else{
					printf("-----pid:%d------get %d bytes of content: %s\n", getpid(), ret, buf);
				}
				
			}
		}else{
			printf("something else happen\n");
		}
		
	}
	
}


int main(int argc, char *argv[])
{
	if(argc <= 2){
		printf("Usage: %s ip_address port_number\n", argv[0]);
		return 1;
	}
	const char *ip = argv[1];
	int port = atoi(argv[2]);
	
	int ret = 0;
	struct sockaddr_in address;
	bzero(&address, sizeof(address));
	
	address.sin_family = AF_INET;
	inet_pton(AF_INET, ip, &address.sin_addr);
	address.sin_port = htons(port);
	
	int listenfd = socket(AF_INET, SOCK_STREAM, 0);
	assert(listenfd >= 0);
	
	ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
	assert(ret != -1);
	
	int opt = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

	
	ret = listen(listenfd, 5);
	assert(ret != -1);
	printf("-----main process------pid:%d, ret:%d\n", getpid(), ret);
	
	
	pid_t pid;
	int i = 0;
	for(i = 0; i < PROCESS_NUM; i++)
	{
		pid = fork();
		
		if( 0 == pid )		//子进程正在运行
		{
			printf("-----fork sucess------pid:%d-----------\n", getpid());	
			break;
		}
	}
	
	if(pid == 0)
	{
		struct epoll_event events[MAX_EVENT_NUMBER];
		int epollfd = epoll_create(5);
		assert(epollfd != -1);
		addfd(epollfd, listenfd, true);
		
		while(1){
			int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
			if(ret<0){
				printf("epoll failure\n");
				break;
			}
			//lt(events, ret, epollfd, listenfd);
			et(events, ret, epollfd, listenfd);
		}
	}else{
		while(1){
			sleep(1000);
		}
	}
	
	
	close(listenfd);
	
	return 0;
}

       编译成功后,利用strace命令执行进程记录进程的系统调用日志如下:

         再查看一下追踪日志如下:

         由上述追踪日志可得:主进程fork出的三个子进程在一个连接到来时同事被唤醒去accept连接,只有一个进程的accept成功,另外两个进程返回EAGAIN错误码即没有争取到连接,这种现象为惊群现象。

IO多路复用---epoll详解及示例 一、epoll系统调用 epoll是Linux特有的I/O复用函数。它的实现和使用上与select、poll有很大的差异。注意epoll是使用一组函数来完成任务的,而不是单个函数。其次,epoll把用户关心的文件描述符上的事件放在内核的一个事件表里面,从而无需像select和poll那样每次调用都要重复传入文件描述符或事件集。 二、内核事件表 首先,epoll需要使用一个额外的文件描述符,来唯一表示内核中的这个事件表,这个文件描述符使用如下函数epoll_create函数来创建: #include&lt 阅读详情

相关推荐

epoll经典代码示例

1. epoll原理 原理性的知识不再另做说明,我在这里附上收藏整理的两篇经典文章: select与epoll的本质关系。 select、poll、epoll之间的区别。 2. epoll服务器端经典示例 2.1 逻辑框架 2.2 代码 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <asse.

遇见你是我最美丽的意外 9502

epoll完整实例

转自:http://www.cppblog.com/API/archive/2013/07/01/201424.html epoll完整实例 #include deque> #include map> #include vector> #include pthread.h> #include semaphore.h> #include time.h> #includ

liuhongxian的专栏 1797

使用C++构建高性能网络服务

基于epoll与线程池实现的C++网络库,其中服务端采用线程间共享互斥锁避免惊群效应。详解架构设计、异步日志、支持非阻塞IO的缓冲。阅读时长约20min。 从Nginx说起 Nginx是使用多进程与IO多路复用实现的高性能C++服务端,但是Nginx为什么要使用多进程,而不是多线程呢? 子线程与子进程的主要区别在于子进程有独立的PID与地址空间,独立PID在并发编程时使得信号的处理更加方便,因为信号根据种类不同,作用于线程组时的行为也有很大的不一样。但由于父子进程各自的地址空间是独立的,所以nginx

lisheng0947的博客 3383

epoll简单示例

简单的epoll实例。

qq_42702222的博客 2633

Android消息机制底层揭秘:Looper如何用eventfd+epoll实现零延迟唤醒?

本文深入剖析了Android消息机制底层原理,重点揭示Looper如何利用Linux内核的eventfd与epoll组合实现零延迟唤醒。通过分析Native层Looper的初始化、核心循环及唤醒流程,详解了eventfd作为轻量级事件计数器与epoll作为高效多路监听器的协同工作机制,并探讨了EFD_NONBLOCK标志、惊群效应规避等关键设计细节。

weixin_28314575的博客 321

epoll示例

其中最后一个成员 sin_zero 是一个填充字段,其目的是为了保证 struct sockaddr_in 结构体的总大小和 struct sockaddr 结构体的大小相同,因为在socket API中,地址通常是通过 struct sockaddr 类型来传递的。在许多实现中,这个填充可能并不是严格必要的,因为sockaddr_in和sockaddr的转换通常都能正常工作,但按照好的编程习惯,仍然建议对这部分内存进行清零处理。想要在一个进程中管理多个到同一个服务器的连接,不需要为每个连接创建新的进程。

数字人生 1746

nginx为什么是多进程单线程和多路IO复用模型

Nginx现在是非常火爆的web服务器,她使用更少的资源,支持更多的并发连接数,她实现了linux的epoll模型,能够支持高达50,000个并发连接数的响应。Nginx采用的是多进程单线程和多路IO复用模型。使用了I/O多路复用技术的Nginx,就成了”并发事件驱动“的服务器。这里再强调下重点, 多进程单线程 多路IO复用模型 一、多进程单线程 Nginx 自己实现了对epoll的封装,是多进程单线程的典型代表。使用多进程模式,不仅能提高并发率,而且进程之间是相互独立的,一 个worker进..

qq422431474的博客 6030

AM驱动架构—优质Mini-LED显示技术解决方案

(VR)和增强现实(AR)设备:MiniLED背光驱动方案适用于虚拟现实和增强现实设备中的显示技术,通过高亮度和高对比度的显示效果,创造更逼真的虚拟体验此外,MiniLED背光还能够实现更快的响应速度,减少图像残影,使得用户的动作在虚拟环境中更加流畅自然。以其出色的性能和广泛的应用前景备受关注是实现MiniLED显示效果的重要环节。1)更高的亮度和对比度:尺寸更小,可以在同样面积的显示屏上配置更多的LED,并且通过独立控制每一个MiniLED,可以实现更高的亮度和更细致的对比度,使得显示画面更加鲜明清晰。

isweekDN的博客 2762

计算机网络【EPOLL 源码详解】

当我们通过epoll_create创建一个epoll实例,然后通过epoll_ctl来添加,移除或者修改对文件的就绪事件的监听。可以看到添加的逻辑,主要就是调用file的poll方法,设置添加回调函数到file的等待队列,并且如果已经有正在监听的就绪事件,则唤醒正在等待队列中的线程。是一个file,那么当不再使用的时候,需要被close掉,就像我们close一个打开的文件一样。,将不会再监听任何事件,并且没有被从epoll中移除,如果需要重新监听,需要使用。那些支持接口的语言,背后的实现无外乎也就是这样。

谢谢大家的关注 1564

拓扑优化背后的科学:ANSYS Workbench中的算法与原理

本文深入解析了ANSYS Workbench中拓扑优化的科学原理与算法实现,重点介绍了密度法(SIMP)和灵敏度分析等核心算法。通过分层架构解析和参数调优实战,展示如何有效进行模型验证与多物理场耦合优化,帮助工程师提升结构设计效率与性能。

weixin_29250403的博客 417

Verilog数字钟

使用Verilog语言进行的数字钟的编写,FPGA开发板亲测正常 大学生一个,第一次自己做的东西,写出来分享在这里插入代码片一下,有不对的地方请大家批评指正。 这次的做的题是:设计一个数字钟,该数字钟能进行正常的时,分计时功能,且能对数字钟的时间进行调整。 该数字钟有3个按键,按键1为功能键,用于数字钟在“正常计时”,“时调整”,“分调整”这三个功能之间切换;按键2,3用于调整时间,仅当数字钟处于...

bin_bin_bun的博客 6149

一个解决了惊群效应的高并发主动式服务模型

       网络框架,服务端必不可少的listen监听socket描述符,监听是否有新连接请求,讨论一个问题,多进程或者多线程模型中,listen到底该如何放置?以多线程为例,一般情况下分为mainThread(主线程)和workThread(工作线程),mainThread负责进程启动结束等一系列其他的主要控制流程任务,workThread负责具体业务逻辑任务。       场景1:list...

qq_22123167的博客 939

静态时序分析(STA)_深入解析Data to Data Checks的零周期挑战

本文深入解析了静态时序分析(STA)中Data to Data Checks的核心概念与零周期挑战。重点探讨了当Setup要求值超过时钟周期时,工具如何调整发射沿,并导致Hold检查的发射与捕获沿“分裂”这一反直觉现象。文章通过案例详细解读了时序报告,并提供了通过调整约束或接受多周期保持两种策略来应对这一挑战,帮助工程师正确理解和处理此类复杂时序检查。

weixin_29204205的博客 350

剖析 epoll 的事件驱动模型与多路复用原理,并基于此构建可扩展的 Reactor 反应堆框架,打造低延迟、高吞吐量的服务端架构!

本篇将在之前学过的多路转接,多路复用的select,poll的基础上,进行对多路转接之epoll进行学习,从基础使用,到底层原理,到多模式了解,最后基于epoll实现简单版的Reactor反应堆框架。

羑悻的博客. 9015

Chronos:学习时间序列的大语言模型(论文解读)

《Chronos:学习时间序列的大语言模型》论文翻译

qq_20144897的博客 9053

电路保护设计实战:保险丝选型、I²t计算与多级协同方案

电路保护是电子系统可靠性的基石,其核心原理是在发生过流或过压等异常情况时,及时切断故障回路,防止能量持续灌入导致元器件损坏甚至安全事故。从技术价值看,优秀的保护设计能显著提升产品寿命、安全性和市场口碑,尤其在快充、电动汽车等高功率密度应用中,保护器件已成为不可或缺的安全屏障。在众多保护器件中,保险丝作为最基础的过流保护元件,其正确选型与应用直接关系到保护效果。本文聚焦于保险丝选型的实战细节,深入解析了I²t(电流平方时间积)这一关键参数,它定义了熔断所需的能量阈值,是匹配被保护半导体器件耐受能力、实现有效保

weixin_42662171的博客 309

linux 线程异常退出_搞定Nginx高并发原理:多进程单线程和多路IO复用模型

Nginx现在是非常火爆的web服务器,她使用更少的资源,支持更多的并发连接数,实现了linux的epoll模型。Nginx采用的是多进程单线程和多路IO复用模型。使用了I/O多路复用技术的Nginx,就成了”并发事件驱动“的服务器。这里再强调下重点,多进程单线程多路IO复用模型一、多进程单线程Nginx 自己实现了对epoll的封装,是多进程单线程的典型代表。使用多进程模式,不仅能提高并发率,而...

weixin_42668301的博客 196

7.19华为OD机试真题 新系统 - 物流仓储多维度成本利润综合查询系统 (Java/Py/C/C++/Js/Go)

7.19华为OD机试真题 新系统 - 物流仓储多维度成本利润综合查询系统 (Java/Py/C/C++/Js/Go)

算法大师! 616

的核数和进程_搞定Nginx高并发原理:多进程单线程和多路IO复用模型

Nginx现在是非常火爆的web服务器,她使用更少的资源,支持更多的并发连接数,实现了linux的epoll模型。Nginx采用的是多进程单线程和多路IO复用模型。使用了I/O多路复用技术的Nginx,就成了”并发事件驱动“的服务器。这里再强调下重点,多进程单线程多路IO复用模型一、多进程单线程Nginx 自己实现了对epoll的封装,是多进程单线程的典型代表。使用多进程模式,不仅能提高并发率,而...

weixin_36081187的博客 255

C#上位机+YOLOv8实战:摄像头/视频流实时目标检测(工业级优化+完整可运行源码)

纯C#实现,无Python依赖,适配工业工控机部署;工业级优化:解决帧率、内存、鲁棒性三大核心问题;代码完整可复用,支持摄像头/视频流/RTSP多源检测;避坑指南覆盖开发中90%的常见问题,降低落地成本。相较于传统方案,该方案兼顾实时性与易用性,成本仅为商用视觉库的1/10,可快速落地到工业质检、安防、物流等场景。后续可拓展方向包括:自定义数据集训练、GPU加速推理、工业通信(PLC/串口)对接等。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 732

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

端口复用

端口复用 Linux 3.9以上   使用 so_reuseport 选项即可 Linux 3.9以下 非listen态的端口复用   so_addrreuse 解决 listen态的socket   多个进程accept 同一个listen_socket实现端口复用 惊群   Linux 2.6以下是有惊群效应,当一个连接到来时,内核会唤醒该listen_socket上调用accept函数的所有...

我喜欢雨天的清新的博客 731

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

nginx事件模型小结

大家看到这篇文章可能会感觉很失望,居然没什么内容。。。写这篇的目的是给出一些我认为比较好的文章,做一下补充和概括。这里不写重复的内容,只是转发一下连接供大家参考。 1. http://www.tbdata.org/archives/1214 这是淘宝yixiao的分析文章,这篇给出了nginx事件抽象模型的基础构建,为事件循环的正是运作打基础。 2. http://www.tbdata...

云计算? 113

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

结合Nginx工作流程理解Epoll机制和Reactor模型

注意:为了避免惊群效应,现在的nginx采用的是锁机制或者SO_REUSEPORT机制,确保一个监听Socket只由有一个Worker线程负责。惊群效应:当监听Socket有新的连接请求进来时,所有的Worker进程都会被唤醒,但最终只会有一个Worker线程拿到请求。

weixin_47697233的博客 358

从阻塞 IO 到 epoll:把 Linux 网络 IO 一次讲透

本文系统梳理了Linux网络IO的核心机制,从阻塞IO到epoll的实现原理。首先解析IO操作的本质分为等待数据就绪和数据拷贝两个阶段,重点分析了不同IO模型的区别。深入探讨了IO多路复用技术如何通过一个线程同时监控多个文件描述符,详细剖析select/poll的底层实现机制及其性能瓶颈。特别揭示了内核等待队列的工作方式,以及select/poll每次被唤醒后必须全量遍历所有fd的设计缺陷,为理解epoll的优化原理奠定基础。全文以清晰的逻辑主线串联起Linux网络IO的关键概念,帮助读者建立系统性的认知框

有趣的中国人的博客 645

深入解析Linux IO多路复用:从select到epoll的高并发网络编程实践

IO多路复用是操作系统提供的一种高效I/O事件通知机制,其核心原理在于允许单个进程或线程同时监视多个文件描述符(如网络套接字)的状态变化。通过将文件描述符集合的管理委托给内核,并在事件就绪时接收通知,该技术能够将传统阻塞I/O模型中的“一对一”服务模式转变为“一对多”的监控模式,从而在资源利用率和并发处理能力上实现质的飞跃。其技术价值在于,它能够以极少的系统线程支撑海量的并发连接,显著降低上下文切换和内存开销,成为构建高性能、可扩展网络服务的基石。这一技术广泛应用于Web服务器、即时通讯、游戏服务器、金融交

weixin_34380948的博客 323

总结了一下中继引擎(can中继器,TCP总机器)开发实际经验

中继器,或者中继引擎的开发实战经验 小结 多路数据进行中继的研究。

萧西朗的博客 1226
上一篇: 线程池实现简单案例(C语言)
下一篇: DPDK简单案例
码农诗人
博客等级 码龄11年 987粉丝 84原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值