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月前
|
JavaScript UED
常见的触发函数的事件(实现不同的用户体验)
常见的触发函数的事件(实现不同的用户体验)
63 0
|
7月前
|
网络协议
Epoll事件ET和LT模型分析
Epoll事件ET和LT模型分析
75 0
|
2月前
|
JavaScript 前端开发
|
6月前
|
监控 Linux
LT模式下epoll一直通知可写怎么办?
LT模式下epoll一直通知可写怎么办?
48 1
如何让touchmove之后不触发touchend的事件
如何让touchmove之后不触发touchend的事件
67 0
|
7月前
|
监控 大数据 应用服务中间件
epoll的水平触发LT以及边沿触发ET的原理及使用及优缺点
epoll的水平触发LT以及边沿触发ET的原理及使用及优缺点
284 0
|
存储 缓存 监控
I/O多路复用中的水平触发和边缘触发
I/O多路复用中的水平触发和边缘触发
226 0
自下而上自动循环滚动
自下而上自动循环滚动
121 0
自下而上自动循环滚动
|
弹性计算 运维 监控
更新应用时,如何实现 K8s 零中断滚动更新?
Kubernetes 集群中,业务通常采用 Deployment + LoadBalancer 类型 Service 的方式对外提供服务。这种架构部署和运维都十分简单方便,但是在应用更新或者升级时可能会存在服务中断,引发线上问题。今天我们来详细分析下这种架构为何在更新应用时会发生服务中断以及如何避免服务中断。
更新应用时,如何实现 K8s 零中断滚动更新?
彻底学会使用epoll(二)——ET和LT的触发方式
分析了ET和LT的实现方式,那么分析他们的触发方式就容易多了。我们通过实现分析知道LT模式下epoll_wait被唤醒可以通过两种方式(图中红线和蓝线),而ET模式只能通过一种方式(图中红线)。
1510 0