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可读时,其事件能够被触发,进而让其他线程有机会继续处理

目录
相关文章
|
7月前
|
存储 架构师 安全
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
锁状态bits1bit是否是偏向锁2bit锁标志位无锁状态对象的hashCode001偏向锁线程ID101轻量级锁指向栈中锁记录的指针000重量级锁指向互斥量的指针010尼恩提示,讲完 如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等优化手段 , 可以得到 120分了。如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等‌。JVM锁的膨胀、锁的内存结构变化相关的面试题,是非常常见的面试题。也是核心面试题。
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
|
存储 前端开发 安全
C++一分钟之-未来与承诺:std::future与std::promise
【6月更文挑战第27天】`std::future`和`std::promise`是C++异步编程的关键工具,用于处理未完成任务的结果。`future`代表异步任务的结果容器,可阻塞等待或检查结果是否就绪;`promise`用于设置`future`的值,允许多线程间通信。常见问题包括异常安全、多重获取、线程同步和未检查状态。解决办法涉及智能指针管理、明确获取时机、确保线程安全以及检查未来状态。示例展示了使用`std::async`和`future`执行异步任务并获取结果。
399 2
|
7月前
|
存储 编译器 C++
【c++】多态(多态的概念及实现、虚函数重写、纯虚函数和抽象类、虚函数表、多态的实现过程)
本文介绍了面向对象编程中的多态特性,涵盖其概念、实现条件及原理。多态指“一个接口,多种实现”,通过基类指针或引用来调用不同派生类的重写虚函数,实现运行时多态。文中详细解释了虚函数、虚函数表(vtable)、纯虚函数与抽象类的概念,并通过代码示例展示了多态的具体应用。此外,还讨论了动态绑定和静态绑定的区别,帮助读者深入理解多态机制。最后总结了多态在编程中的重要性和应用场景。 文章结构清晰,从基础到深入,适合初学者和有一定基础的开发者学习。如果你觉得内容有帮助,请点赞支持。 ❤❤❤
870 0
|
监控 大数据 应用服务中间件
epoll的水平触发LT以及边沿触发ET的原理及使用及优缺点
epoll的水平触发LT以及边沿触发ET的原理及使用及优缺点
583 0
|
12月前
|
存储 前端开发 API
DDD领域驱动设计实战-分层架构
DDD分层架构通过明确各层职责及交互规则,有效降低了层间依赖。其基本原则是每层仅与下方层耦合,分为严格和松散两种形式。架构演进包括传统四层架构与改良版四层架构,后者采用依赖反转设计原则优化基础设施层位置。各层职责分明:用户接口层处理显示与请求;应用层负责服务编排与组合;领域层实现业务逻辑;基础层提供技术基础服务。通过合理设计聚合与依赖关系,DDD支持微服务架构灵活演进,提升系统适应性和可维护性。
|
12月前
|
NoSQL Java 关系型数据库
阿里 P7二面:Redis 执行 Lua,到底能不能保证原子性?
Redis 和 Lua,两个看似风流马不相及的技术点,为何能产生“爱”的火花,成为工作开发中的黄金搭档?技术面试中更是高频出现,Redis 执行 Lua 到底能不能保证原子性?今天就来聊一聊。 
382 1
|
算法 Java Sentinel
限流算法(计数器、滑动时间窗口、漏斗、令牌)原理以及代码实现
> 本文会对这4个限流算法进行详细说明,并输出实现限流算法的代码示例。 > 代码是按照自己的理解写的,很简单的实现了功能,还请大佬们多多交流找bug。
1690 0
|
消息中间件 缓存 Kafka
原理剖析| 一文搞懂 Kafka Producer(上)
本文介绍了Apache Kafka 3.7的Producer使用及原理,讲解了如何创建和使用Producer,展示了一个发送消息的示例代码,并介绍了ProducerRecord和Callback接口。ProducerRecord包含topic、partition等属性,Callback用于发送消息后的回调处理。接着阐述了send、flush和close方法的功能。文章还探讨了核心组件,包括ProducerMetadata、RecordAccumulator、Sender和TransactionManager,以及消息发送流程。最后,讨论了元数据刷新、分区选择、消息攒批和超时处理等实现细节。
660 0
原理剖析| 一文搞懂 Kafka Producer(上)
|
存储 分布式计算 安全
大数据存储技术(2)—— HDFS分布式文件系统
大数据存储技术(2)—— HDFS分布式文件系统
1982 0
|
安全 程序员 编译器
【C++ 异常 】深入了解C++ 异常机制中的 terminate()处理 避免不必要的错误(一)
【C++ 异常 】深入了解C++ 异常机制中的 terminate()处理 避免不必要的错误
977 1