epoll的水平触发(LT)和边缘触发模式(ET)详解

简介: epoll的水平触发(LT)和边缘触发模式(ET)详解

概述:

本文介绍epoll的水平触发和边缘触发模式的具体工作原理,以及处理多线程下的线程安全问题

假定读者了解epoll以及其系统调用epoll_create, epoll_ctl, epoll_wait的基本使用

struct epoll_event ev;//声明一个事件,一个事件由事件类型(可读?可写?触发模式?)和数据(data.fd指定这个事件所从属的目标文件描述符)组成
ev.events = EPOLLIN | EPOLLET;//位运算注册事件类型: EPOLLIN(可读),EPOLLOUT(可写), EPOLLET(ET模式), EPOLLLT (LT模式) 

LT(水平触发)

LT是默认的工作模式,对于采用LT的文件描述符,在水平触发模式下,如果某个文件描述符上有数据可读,内核会持续通知应用程序,直到应用程序处理完数据或者缓冲区不再有数据可读为止。当调用epoll_wait检测到其上有事件发生并通知应用程序时,应用程序可以不立即处理完毕该事件。这样,当程序下一次调用epoll_wait时,epoll_wait还会向应用程序通知此事件,直到事件被处理完毕

应用场景:解析http报文,分次解析http报文

ET(边缘触发)

ET比LT效率高,对于使用ET模式的文件描述符,在边缘触发模式下,只有在文件描述符状态变化(对于EPOLLIN事件,只有在状态从未就绪变化为就绪,才叫做变化)发生时才会触发事件,所以调用epoll_wait检测到其上有事件发生,并通知应用程序,应用程序必须立即处理完毕该事件,否则会造成数据丢失,因为后续的epoll_wait调用不再重复向应用程序通知此事件

应用场景:读取大型文件,用while循环一次全部读取完毕

 while (1) {
                char buffer[10] = {0};
                int count = recv(events[i].data.fd, buffer, 10, 0);
 
                if (count < 0) {//读取完毕或当前没有数据可读或者出错
 
                    if( (errno == EAGAIN) || (errno == EWOULDBLOCK)) {//读取完毕
                    printf("recv finished\n");
                    break;
                    }
                    //recv出错
                    close(events[i].data.fd);//关闭事件的套接字
                    break;
                } 
                else if (count == 0) {//对方断开连接
 
                    epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &ev);//移除该事件
                    close(events[i].data.fd);//关闭事件的套接字
                    break;
                }
                else {//接收到数据
 
                    printf("recv from clientfd: %d, count: %d, data: %s", events[i].data.fd, count, buffer);
                }
 
}

此外,使用ET模式的文件描述符应该是非阻塞的,否则会阻塞在epoll_wait();

总的来说,

在水平触发模式下,应用程序需要持续处理就绪的文件描述符,直到文件描述符上不再有数据可读为止。相比之下,在边缘触发模式下,应用程序只会在状态变化时收到通知,而不会持续收到通知直到处理完数据。

重点来了

考虑一种情况:在ET模式下,一个文件描述符上有数据可读(EPOLLIN触发),线程开始处理数据,而在处理过程中又有新数据加入。当旧数据开始处理时,文件描述符仍然保持在就绪状态。但当有新的数据写入时,文件描述符会从就绪状态变为未就绪状态,然后再次变为就绪状态,触发一次新的 EPOLLIN 事件。这样可以确保即使在旧数据处理过程中有新的数据写入,应用程序也能及时地得到通知,并读取新的数据。在单线程上,此时只要程序循环读取数据就不会造成新数据的丢失,而对于多线程就需要特殊处理(详见下EPOLLONESHOT

LT 模式下,新数据的写入不会改变文件描述符的状态码。如果文件描述符上有数据可读,它的状态码会一直保持就绪状态,直到所有的数据都被读取完毕才会变为未就绪。即使在读取数据的过程中有新的数据写入,文件描述符的状态码仍然会保持就绪状态,不会因为新数据的写入而改变

EPOLLONESHOT

即使使用了ET模式,一个socket上的事件还是可能被触发多次。

例如:当一个线程处理一个socket时有新数据写入,此时另外一个线程被唤醒读取这些数据,于是出现了两个线程同时操作一个socket的情况。EPOLLONESHOT解决了多个线程同时操作一个socket的问题,对于注册了EPOLLONESHOT的事件,操作系统最多触发其上注册的一个可读可写异常事件,且只触发一次。这样,一个线程在操作这个socket时,其他线程不可能有机会操作该socket。但反过来思考,注册了EPOLLONESHOT事件的socket,一旦被某个线程处理完毕,要及时修改为EPOLLIN或其他事件,以确保下次这个socket可读时,其事件能够被触发,进而让其他线程有机会继续处理

目录
相关文章
|
9月前
|
JavaScript UED
常见的触发函数的事件(实现不同的用户体验)
常见的触发函数的事件(实现不同的用户体验)
73 0
|
7月前
|
运维 Java Serverless
函数计算产品使用问题之事件函数单实例的并发度默认是多少
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
143 6
函数计算产品使用问题之事件函数单实例的并发度默认是多少
|
6月前
|
消息中间件 运维 Serverless
函数计算产品使用问题之如何判断从函数调用获取到的事件是由哪个触发器发出的
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
9月前
|
JSON 前端开发 算法
2715. 执行可取消的延迟函数
2715. 执行可取消的延迟函数
56 0
|
9月前
|
监控 大数据 应用服务中间件
epoll的水平触发LT以及边沿触发ET的原理及使用及优缺点
epoll的水平触发LT以及边沿触发ET的原理及使用及优缺点
313 0
|
监控 程序员 C++
[虚幻引擎] UE里面监控每帧循环里面 C++ 函数的性能,监控函数效率,函数执行时间。
在使用C++开发UE引擎,有时候需要监控函数的执行的执行效率,这个时候有两种方式可以使用。
227 0
|
存储 缓存 监控
I/O多路复用中的水平触发和边缘触发
I/O多路复用中的水平触发和边缘触发
248 0
通过定时器T1查询方式控制LED1周期性闪烁(自由计数模式)
通过定时器T1查询方式控制LED1周期性闪烁(自由计数模式) 宏定义与函数声明 初始化 主函数
294 0
通过定时器T1查询方式控制LED1周期性闪烁(模模式)
通过定时器T1查询方式控制LED1周期性闪烁(模模式) 宏定义与函数声明 初始化 主函数
332 0
|
弹性计算 运维 监控
更新应用时,如何实现 K8s 零中断滚动更新?
Kubernetes 集群中,业务通常采用 Deployment + LoadBalancer 类型 Service 的方式对外提供服务。这种架构部署和运维都十分简单方便,但是在应用更新或者升级时可能会存在服务中断,引发线上问题。今天我们来详细分析下这种架构为何在更新应用时会发生服务中断以及如何避免服务中断。
更新应用时,如何实现 K8s 零中断滚动更新?