Linux中 I/O 多路复用机制的边缘触发与水平触发
边缘触发(Edge Triggered, ET)与水平触发(Level Triggered, LT)
Linux中I/O复用机制epoll -CSDN博客
Linux中的 I/O 复用机制 select-CSDN博客
在 epoll
或其他 I/O 多路复用机制中,触发模式是指如何触发文件描述符的事件。触发模式决定了何时会通知程序处理该文件描述符的 I/O 操作。常见的触发模式有 边缘触发(ET) 和 水平触发(LT)。
1. 水平触发
水平触发(LT) 是最常见的触发模式,也是 select
和 poll
默认使用的模式。在该模式下,事件触发是基于文件描述符的 当前状态,也就是说,只要某个文件描述符的状态保持满足条件,epoll_wait()
会一直返回这个事件,直到应用程序处理该事件。
- 如果不理解可以看后面的例子
-
适用场景:适用于大多数应用程序,特别是那些对高性能要求不那么严格的应用。
-
工作原理:每次调用
epoll_wait()
时,系统都会检查文件描述符的状态。如果文件描述符处于就绪状态(比如可以读取或写入),即使应用程序没有及时处理,epoll_wait()
依然会返回该文件描述符,直到应用程序读取了数据或者完成了某项操作。
优点:处理逻辑简单。只要文件描述符满足条件,epoll_wait()
就会一直返回它,直到应用程序处理该事件。
稳定,适用于一般的多路复用场景。
缺点:对于高性能应用,可能会造成不必要的重复通知,特别是当文件描述符的状态没有改变时,epoll_wait()
会多次返回同一个文件描述符。
示例: 在水平触发模式下,如果一个套接字上的数据未被读取,epoll_wait()
会每次返回该文件描述符,直到数据被完全读取。
2. 边缘触发
边缘触发(ET) 模式的工作方式与水平触发不同。在该模式下,只有当文件描述符的状态发生变化时(比如从“未就绪”变为“就绪”),epoll_wait()
才会通知应用程序。简而言之,只有在事件边缘发生时,才会触发通知,即当文件描述符从不可用变为可用时触发一次通知。
-
适用场景:适用于高性能场景,尤其是在大规模并发连接下,能够减少不必要的轮询。
-
工作原理:当文件描述符从不可用变为可用时,
epoll_wait()
会返回该文件描述符的事件。然而,一旦返回,epoll_wait()
不会再次返回相同的事件,直到文件描述符的状态发生变化(例如,数据被读取或者写入)。
优点:边缘触发模式能够显著减少不必要的通知,适用于处理大量并发连接时,提高系统性能。
缺点:边缘触发模式要求应用程序在处理文件描述符的事件时必须迅速处理完毕(即一次性读取尽可能多的数据)。如果应用程序未及时处理,可能会错过后续的通知。
示例: 假设有一个套接字,且 epoll
采用边缘触发模式。当某个套接字上的数据准备好时,epoll_wait()
会一次性返回该套接字的事件。如果应用程序在第一次通知时未完全读取数据,下次 epoll_wait()
不会返回该事件,直到套接字的状态发生改变(如再次有数据可读)。这要求应用程序必须一次性读取所有数据,否则可能错过后续事件。
这里来总结一下水平触发和边缘触发:
-
水平触发 (LT): 只要某个条件持续满足(比如,一个套接字上有数据可读),系统就会在你每次检查的时候(例如调用
epoll_wait
)持续通知你,直到这个条件不再满足(比如,你已经把所有数据都读完了)。它就像一个传感器,只要触发条件(“水平”)存在,它的指示灯就一直亮着。 -
边缘触发 ( ET): 仅当文件描述符的状态发生变化时(比如,从“不可读”变为“可读”,即新数据第一次到达一个之前是空的或已读完的套接字时),系统才会通知你一次。即使你没有完全处理这次事件中的所有数据,对于这批“旧”数据,你也不会收到后续的通知,除非又有新的事件(新的“边缘”)发生。它就像一个传感器,仅在触发条件刚刚发生或改变的那一刻,它才会闪烁一下或发送一个脉冲信号。
一个简单的例子:你的信箱
想象一下你有一个信箱(相当于一个文件描述符,比如套接字),你在等信件(数据)。
-
水平触发 (LT) - “小红旗一直竖着”的信箱:
-
你的信箱上有一个小红旗。当邮递员投递了信件后,他会把小红旗竖起来。
-
工作方式:
-
邮递员送来了 3 封信。小红旗竖起来了。
-
你看了一眼信箱(类似调用
epoll_wait
):你看到小红旗是竖着的(条件满足)。系统通知你。 -
你打开信箱,拿走了 1 封信,但因为有事耽搁了,还剩 2 封信在里面。
-
过了一会儿,你又看了一眼信箱:小红旗仍然是竖着的(因为里面还有信)。系统又通知了你一次。
-
你把剩下的 2 封信也拿走了。现在信箱空了。小红旗放下了。
-
你再看信箱:小红旗是放下的。没有通知。
-
-
核心点: 对于 LT 模式,只要还有数据可读(信箱里还有信),
epoll_wait
就会持续提醒你。这种模式比较“宽容”,即使你一次没有处理完所有数据,后续还会收到提醒。
-
-
边缘触发 (ET) - “按门铃”的信箱:
-
邮递员在每次投递了新的信件后,会按一次门铃。如果你没开门,或者开门只拿了一部分信,他不会因为信箱里还有上次没拿完的信而一直按门铃。
-
工作方式:
-
邮递员送来了 3 封信。他按了一次门铃(状态从“没有新信”变成了“有新信到达”)。
-
你听到了门铃声(类似
epoll_wait
返回了事件)。系统通知你。 -
你去了信箱,拿走了 1 封信,但因为有事又耽搁了,还剩 2 封信在里面。
-
你回去继续等待(再次调用
epoll_wait
):对于信箱里那 2 封已经存在的信,门铃不会再次响起。因为“新信到达”这个事件已经发生过了,并且已经通知过你了。 -
如果你在第一次听到门铃后没有彻底清空信箱,那么剩下的 2 封信你可能就会“遗漏”,直到邮递员下次送来新的信件并再次按门铃。
-
后来,邮递员又送来了 2 封新的信。他又按了一次门铃。这时你去看信箱,会发现里面有 4 封信(之前剩下的 2 封 + 这次新的 2 封)。
-
-
核心点: 对于 ET 模式,当数据首次到达(或者说状态变为就绪)时,你会收到一次通知。你必须在收到这次通知后,把所有可用的数据都处理掉(例如,在一个循环里从套接字读取数据,直到
read
返回错误如EAGAIN
或EWOULDBLOCK
,表示暂时没有更多数据了)。如果你不这样做,对于这次事件中剩余未处理的数据,系统不会再次提醒你。
-
简单来说:
-
LT 就像是你问:“有信吗?” 只要有信,你每次问都会得到“有”的答复。
-
ET 就像是每次有新信来的时候,邮差会喊一声:“来新信啦!”只喊这一次。
3. LT 与 ET 的区别总结
特性 | 水平触发(LT) | 边缘触发(ET) |
---|---|---|
事件触发时机 | 只要文件描述符满足条件,都会触发通知 | 只有状态发生变化时触发一次通知 |
通知的次数 | 只要状态不改变,每次调用 epoll_wait() 都会通知 | 只有状态发生改变时,才会触发一次通知 |
适用场景 | 适用于大多数应用,处理逻辑简单 | 适用于高并发、高性能应用,需要开发者精心管理事件 |
数据处理 | 可以逐步读取数据(每次触发都可以继续读取) | 必须一次性读取完数据,否则可能错过事件 |
4. 结合代码例子:ET 和 LT 模式的区别
假设你有一个 TCP 服务器,需要处理来自客户端的多个连接请求。下面是两种模式的基本实现方式:
水平触发模式(LT)
// 水平触发模式:每次调用 epoll_wait 时,都会返回所有已就绪的文件描述符
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) {int event_count = epoll_wait(epfd, events, maxevents, timeout);for (int i = 0; i < event_count; i++) {int fd = events[i].data.fd;if (FD_ISSET(fd, &readfds)) {int len = read(fd, buf, BUF_SIZE);if (len == 0) {// 客户端关闭连接close(fd);} else {// 处理数据write(fd, buf, len);}}}
}
边缘触发模式(ET)
// 边缘触发模式:只会通知一次,直到文件描述符状态发生变化
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) {int event_count = epoll_wait(epfd, events, maxevents, timeout);for (int i = 0; i < event_count; i++) {int fd = events[i].data.fd;if (FD_ISSET(fd, &readfds)) {int len;while ((len = read(fd, buf, BUF_SIZE)) > 0) {write(fd, buf, len);}if (len == 0) {// 客户端关闭连接close(fd);}}}
}
在 LT 模式中,如果某个套接字可读,epoll_wait()
会每次返回这个套接字,直到应用程序处理了所有数据。而在 ET 模式中,如果某个套接字可读,epoll_wait()
只会在数据第一次准备好时通知应用程序,之后直到该套接字的状态发生变化才会再次通知。
5. 总结
-
水平触发(LT) 更适用于通用应用场景,它的逻辑简单且可靠,每次事件都可以重复触发,直到事件得到处理。
-
边缘触发(ET) 是一种高性能的 I/O 复用机制,适用于高并发和高吞吐量的应用,它通过减少不必要的通知提高了性能,但要求应用程序必须一次性处理所有就绪数据。
选择 ET 还是 LT 取决于应用场景的需求。如果需要处理高并发连接并且程序能够高效地处理 I/O 事件,ET
是一个好的选择;如果是常规应用并且对性能要求不高,LT
会更简单和安全。