## 了解 Linux 中协程 (Coroutine) 切换的原理,包括它作为轻量级上下文切换的优势。深入探讨协程的用户级管理、协作式调度,以及与线程切换的性能对比。{alertInfo}
由人工编写审核,非AI生成内容,请放心观看!
{getToc} $title={文章目录}
前提提要:
LT and ET 是电气学方面的概念,也就是水平触发方式和边缘状态触发方式的关系
ET:每次在的fd 文件描述符号状态变化的时候获取通知,也就是:从的有数据到数据为空的状态通知一次,文件描述符变化。
LT : 指的是 文件描述符满足条件时都会持续触发通知,只有有数据在缓存中,read 没有把所有的数据都取出来,那么下一次epoll_wait 还是回通知你。
| 特性 | 边缘触发(ET) | 水平触发(LT) |
|---|---|---|
| 事件触发频率 | 只会触发一次,直到文件描述符的状态发生变化 | 每次满足条件时都会触发事件(直到数据处理完为止) |
| 数据读取要求 | 必须在每次通知时读取所有数据,否则后续不会收到通知 | 可以逐步读取数据,epoll 会持续通知直到数据全部读取 |
| 适用场景 | 高性能、高并发的事件驱动场景,例如 Web 服务器 | 简单的 I/O 操作,如文件监控或简单的网络应用 |
| 编程复杂度 | 稍高,需要确保每次通知时处理完所有数据 | 较低,只需要按需处理数据 |
| 性能 | 高效,减少了冗余通知,适用于高并发应用 | 可能会导致重复通知,增加 CPU 消耗 |
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <string.h>
#define MAX_EVENTS 10
// 设置文件描述符为非阻塞模式
int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl");
return -1;
}
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1) {
perror("fcntl");
return -1;
}
return 0;
}
// 处理客户端请求的函数
void handle_client_request(int fd) {
char buf[512];
ssize_t bytes_read = read(fd, buf, sizeof(buf));
if (bytes_read == -1) {
if (errno == EAGAIN) {
// 数据读取完毕
return;
}
perror("read");
} else if (bytes_read == 0) {
// 客户端关闭连接
printf("Connection closed by client\\n");
close(fd);
} else {
buf[bytes_read] = '\\0'; // Null-terminate the string
printf("Received request: %s\\n", buf);
// 假设我们处理完请求并准备响应
}
}
int main() {
int epoll_fd, nfds;
struct epoll_event ev, events[MAX_EVENTS];
int server_fd; // 服务器监听的套接字文件描述符
// 创建 epoll 实例
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 假设 server_fd 已经通过 socket() 和 bind() 配置好,并且开始监听
// 设置 server_fd 为非阻塞
if (set_nonblocking(server_fd) == -1) {
exit(EXIT_FAILURE);
}
// 将 server_fd 添加到 epoll 实例,并设置为边缘触发模式
ev.events = EPOLLIN | EPOLLET; // EPOLLIN: 可读事件,EPOLLET: 边缘触发
ev.data.fd = server_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
while (1) {
nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (int i = 0; i < nfds; i++) {
if (events[i].events & EPOLLIN) {
// 处理客户端请求
handle_client_request(events[i].data.fd);
}
}
}
close(epoll_fd);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <string.h>
#define MAX_EVENTS 10
// 设置文件描述符为非阻塞模式
int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl");
return -1;
}
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1) {
perror("fcntl");
return -1;
}
return 0;
}
// 处理文件读取的函数
void handle_file_read(int fd) {
char buf[512];
ssize_t bytes_read = read(fd, buf, sizeof(buf));
if (bytes_read == -1) {
if (errno == EAGAIN) {
// 数据读取完毕
return;
}
perror("read");
} else if (bytes_read == 0) {
// 文件末尾
printf("EOF reached\\n");
} else {
buf[bytes_read] = '\\0'; // Null-terminate the string
printf("New data in file: %s\\n", buf);
}
}
int main() {
int epoll_fd, nfds;
struct epoll_event ev, events[MAX_EVENTS];
int file_fd; // 监控的文件文件描述符
// 打开文件进行监控
file_fd = open("logfile.txt", O_RDONLY);
if (file_fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
// 设置文件描述符为非阻塞模式
if (set_nonblocking(file_fd) == -1) {
exit(EXIT_FAILURE);
}
// 创建 epoll 实例
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 将文件描述符添加到 epoll 实例,并设置为水平触发模式
ev.events = EPOLLIN; // EPOLLIN: 可读事件(水平触发)
ev.data.fd = file_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, file_fd, &ev) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
while (1) {
nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (int i = 0; i < nfds; i++) {
if (events[i].events & EPOLLIN) {
// 处理文件读取
handle_file_read(events[i].data.fd);
}
}
}
close(epoll_fd);
close(file_fd);
return 0;
}对比分析:
ET 实现逻辑: 每次通知时,必须一次性读取所有数据(直到没有更多数据为止)。如果不这样做,epoll 不会再通知你有数据可读了。
void handle_client_request(int fd) {
char buf[512];
ssize_t bytes_read;
while (1) {
bytes_read = read(fd, buf, sizeof(buf));
if (bytes_read == -1) {
if (errno == EAGAIN) {
// 数据读取完毕
return;
}
perror("read");
return;
}
if (bytes_read == 0) {
// 客户端关闭连接
printf("Connection closed by client\\n");
close(fd);
return;
}
// 处理读取到的数据
buf[bytes_read] = '\\0'; // 确保数据以 null 结尾
printf("Received request: %s\\n", buf);
}
}LT 的实现逻辑是:读取数据,即使没有读完,也可以退出,下一次还是会触发的要求你调用的读取函数的。
void handle_file_read(int fd) {
char buf[512];
ssize_t bytes_read = read(fd, buf, sizeof(buf));
if (bytes_read == -1) {
if (errno == EAGAIN) {
// 数据读取完毕
return;
}
perror("read");
} else if (bytes_read == 0) {
// 文件末尾
printf("EOF reached\\n");
} else {
buf[bytes_read] = '\\0'; // Null-terminate the string
printf("New data in file: %s\\n", buf);
}
}- 每次
read()只读取一次数据,但即使数据没有完全读取完,epoll会在数据继续可读时 继续通知程序,直到所有数据被处理完为止。 - 重要: 由于
epoll会持续通知程序,程序不必担心错过数据,只要它持续读取即可
实际使用场景
- 水平触发(LT) 比较适合 简单的、阻塞的程序,如常见的 文件监控 或 低并发网络应用,因为它不要求应用程序在每次通知时都处理完所有数据。
- 边缘触发(ET) 更适合 高并发、高效的事件驱动系统,如 高性能服务器,它减少了通知的次数,提高了性能,但对应用程序的要求更高,必须确保每次事件处理时读写完所有数据。
多次触发性能消耗的原因:
- 如果每次 文件描述符的状态满足条件(如有数据可读、可写)时都会通知应用程序(即 水平触发(LT)),那么每次数据变动时,系统都会调用
epoll_wait()来检查事件。这意味着即使文件描述符的状态没有发生变化,应用程序仍然会被通知,导致系统必须频繁地调用epoll_wait()。 - 每次系统调用都带来一定的性能开销,尤其是 大量的空轮询 会浪费 CPU 时间。
版权声明:感谢您的阅读,资源整理自网络,如果您发现任何侵权行为,请联系 理科生网 管理人员,管理员将及时删除侵权内容。否则均为 理科生网 原创内容,转载时请务必以超链接(而非纯文本链接)标注来源于理科生网及本文完整链接,感谢!{alertInfo}