Event poll 在项目中的应用

水平触发:

Level_triggered(水平触发):当被监控的文件描述符(fd)上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!

边缘触发:

Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!

来源:https://zhuanlan.zhihu.com/p/532789377

场景:

基于gsoap服务;处理短暂的HTTP请求;但是请求非常频繁;需要多线程处理设备发现请求和HTTP接口请求

写法1:

gsoap 服务主要做设备发现的响应和onvif服务的响应;启动任务,启动两条常驻线程(或者线程池启两条线程,在线程任务task里while循环检测是否有socket链接),时刻检测两个fd(设备发现的socket 发的,和http请求的tcp socket fd)上是否有链接请求;soap_server;

1的问题:

使用普通线程常驻:设备发现(加入多播组的UDP socket)也好,onvif的接口请求(HTTP请求,tcp连接)都是短连接,如果客户端多,并且请求频繁的话,线程的真实使用率较高,但是如果只是偶尔几个客户端请求,常驻线程大部分时间都没做事;

使用线程池+while循环:这样做虽然是在使用线程池处理短连接任务,但是为了避免错过任何客户端请求,while循环必须一直运行,也就变相的将线程池中处理短任务的线程,变成长期占用;现成池中的线程长期不能停歇;

基于此,如果服务是多端口的,即进行HTTP响应的端口不止430;(常用的onvif服务都是单端口,但是也可以一个IP加入多播组,多个端口接收设备发现信息,并各自响应onvif请求,客户端发送onvif协议交互,可选择端口)

上边1的开发逻辑,要么常驻N条线程,要么出现线程池中多条线程,并且由于有些线程池是notify_one 的,当一下启动多条线程池任务时候,很可能导致有些任务永远不会被执行(因为线程池中的线程一直被while循环占用,线程池中的task是长任务)

 

综上:要么占用非常多的线程,要么就导致任务无法被处理;基于此,既要解放线程池,又不能错误任何客户端过来的请求;

写法2:EVENT pool (水平触发)+线程池

单独的线程(开一个普通线程处理 event pool 的wait)处理event pool 的wait事件;当接收到 读写事件的时候存入线程池中的task队列;注意区分读写事件;并不是所有类型事件都需要监听,客户端过来的链接只需要监听读事件即可;

event pool 开水平触发模式;

问题:

当启动多个端口,并且有多个客户端过来链接本服务时候,由于wait到的fd的处理事件,是放到任务队列中等待线程池消耗的;就导致任务不是第一时间处理了;没有第一时间wait到就读socket缓冲,根据(“如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读”)就会出现一个wait到的fd的事件一直重复;除非能立即消耗掉fd的这个读写事件,否则会频繁被event_pool wait到,导致线程池的任务队列也会爆慢,任务都是重复的;

这就说明水平触发模式获取到的任务,你需要立即执行;

写法3: EVENT pool (边缘触发)+线程池

单独的线程处理event pool 的wait事件((开一个普通线程处理 event pool 的wait));当接收到 读写事件的时候存入线程池中的task队列;注意区分读写事件;并不是所有类型事件都需要监听,客户端过来的链接只需要监听读事件即可;

//#if defined(_WIN32)
//        unsigned long ul = 1;
//        int ret1 = ioctlsocket(sockfd, FIONBIO, &ul); //#else
//        int ul = 1;
//        int ret1 = ioctl(sockfd, FIONBIO, &ul); 
//#endif //defined(_WIN32)//ev.events = EPOLLIN| EPOLLET;
        ///ev.events = EPOLLIN;//默认水平触发
      

问题:

当有多个客户端都在访问一个gsoap 的onvif服务的时候;只会通知一次,wait到一次;虽然可以免去重复任务被wait到,但是可能当前fd有多个读写事件,每个事件来自不同的客户端,所以这里如果你无法把本次wait到的FD,的内核中的读写事件全部处理完,就会造成有些事件未能及时处理,就会造成,客户端的请求无法及时回复,甚至是下次发请求,回复上一次的请求响应;如图,需要一次性将客户端1,2,5对fd21的请求处理完;也就是socket读尽;一次解析多条请求,分别放入任务队列;

 写法4:水平触发+阻塞任务处理+线程池

使用水平触发,为了避免任务重复,必须等到线程队列中将当前fd的这个当前任务处理完,在wait(接收)本fd的事件;

如:wait到fd =1;的来自客户端1的请求,放入任务队列,记录fd1有待处理任务,等线程池执行完fd=1的任务,标记处理完成;在fd=1;被标记为处理完前wait到的fd=1的事件,全部丢弃;直到处理完fd=1来自客户端1的任务;以此类推,避免重复,又能将多个客户端的任务处理完

其他注意事项:过期列表

如果从epoll_wait(2)返回的所有文件描述符使用事件缓存或存储,         
       则确保提供了一种动态地标记其关闭的方式(即,由先前事件的处理引起)。          
       假设从epoll_wait(2)接收到100个事件,在事件#47中,一个条件导致事件#13关闭。         
       如果你删除结构并close(2)事件#13的文件描述符,         
       那么你的事件缓存可能仍然会说有事件在等待那个文件描述符,从而导致混乱。

       一个解决办法是调用,        
        在事件47的处理期间,epoll_ctl(EPOLL_CTL_DEL)删除文件描述符13并close(2),         
        然后将其关联的数据结构标记为已删除,并将其关联到清除列表。          
        如果在批处理中发现文件描述符13的另一个事件,         
        你会发现文件描述符已经被删除,不会有任何混乱。

posted on   邗影  阅读(14)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示