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

目录
相关文章
|
Java Linux 程序员
Linux平台中调试C/C++内存泄漏方法 (腾讯和MTK面试的时候问到的)
Linux平台中调试C/C++内存泄漏方法 (腾讯和MTK面试的时候问到的)
|
10月前
|
存储 关系型数据库 索引
聚簇索引及其优缺点
聚簇索引是一种数据存储方式,InnoDB通过主键构建B+树组织数据,叶子节点即数据页。若无主键,则选非空唯一索引或隐式创建主键。辅助索引(二级索引)需两次查找:先查主键值,再查数据行。优点是查询快,尤其主键排序与范围查询;缺点是插入依赖顺序,更新主键代价高,且易引发页分裂。
|
缓存 安全 网络协议
HTTP和HTTPS的区别有哪些?
本文简要总结了 HTTP 和 HTTPS 的区别,从概念、端口、连接方式、使用场景、安全性等多个角度进行了对比。HTTP 是无状态的、无连接的应用层协议,适用于一般性网站和性能要求较高的应用;HTTPS 则通过 SSL/TLS 层提供加密、认证和完整性保护,适用于涉及敏感信息和高安全性的场景。文章还讨论了两者在性能上的差异,包括握手和加密开销、缓存效果以及 HTTP/2 的多路复用技术。最终,根据具体需求选择合适的协议能够更好地平衡安全性和性能。
18772 2
HTTP和HTTPS的区别有哪些?
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
2674 32
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
前端开发 JavaScript Java
解决Spring Boot文件上传问题:`MultipartException` 和 `FileUploadException`
解决Spring Boot文件上传问题:`MultipartException` 和 `FileUploadException`
1363 0
|
安全 Java 数据库连接
详细介绍线程间通信
详细介绍线程间通信 线程间通信是指在多线程编程中,不同的线程之间通过某种方式交换信息的过程。这是一个重要的概念,因为线程之间的协作是实现复杂并发系统的关键。 下面是一些线程间通信的常见方式和示例:
2476 0
|
存储 NoSQL Linux
linux积累-core文件是干啥的
核心文件是Linux系统在程序崩溃时生成的重要调试文件,通过分析核心文件,开发者可以找到程序崩溃的原因并进行调试和修复。本文详细介绍了核心文件的生成、配置、查看和分析方法
2031 6
|
存储 关系型数据库 MySQL
图解MySQL【日志】——Undo Log
Undo Log(回滚日志)是 MySQL 中用于实现事务原子性和一致性的关键机制。在默认的自动提交模式下,MySQL 隐式开启事务,每条增删改语句都会记录到 Undo Log 中。其主要作用包括:
998 0
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
894 0
Linux C/C++之IO多路复用(poll,epoll)

热门文章

最新文章