异步I/O和同步I/O
点外卖” 和 “自己做饭” 的生活场景来类比 Linux 中的 异步 I/O(AIO) 和 同步 I/O(Synchronous I/O)
1. 同步 I/O(Synchronous I/O)—— 自己做饭
场景
假设你饿了,决定 自己做饭:
- 第一步:去菜市场买菜(相当于
read()
从磁盘读取数据)- 你必须亲自去菜市场,站在摊位前等老板把菜给你(阻塞)。
- 如果菜市场没菜了,你得一直等(数据未就绪时阻塞)。
- 第二步:回家做饭(相当于 CPU 处理数据)
- 买菜回来后,你才能开始洗菜、切菜、炒菜(顺序执行)。
- 第三步:吃饭(相当于程序继续执行后续逻辑)
- 饭做好后,你才能吃(必须等前一步完成)。
同步 I/O 的特点
- 阻塞:你必须亲自完成每一步,不能同时做其他事(比如不能一边买菜一边炒菜)。
- 顺序执行:必须按
买菜 → 做饭 → 吃饭
的顺序来,不能跳过或并行。 - 简单但低效:如果买菜或做饭很慢,你只能干等着,浪费时间。
Linux 中的同步 I/O 例子
read()
/write()
:直接读写文件,如果数据没准备好,程序会卡住(阻塞)。epoll()
+ 非阻塞 I/O:虽然可以同时监控多个文件描述符,但每次读写仍然需要手动检查数据是否就绪(类似“频繁去菜市场看菜到了没”)。
2. 异步 I/O(Asynchronous I/O, AIO)—— 点外卖
场景
假设你饿了,决定 点外卖:
- 第一步:在手机上下单(相当于提交异步 I/O 请求)
- 你只需要在美团/饿了么上点“提交订单”(非阻塞),然后可以继续刷抖音、打游戏(程序继续执行其他任务)。
- 第二步:外卖小哥送餐(相当于内核在后台处理 I/O)
- 商家接单、做饭、打包、骑手取餐、送货,这些过程你完全不用管(内核在后台完成 I/O 操作)。
- 第三步:收到外卖通知(相当于回调函数或信号通知)
- 手机“叮”一声,提示“外卖已送达”(通过回调或信号通知程序 I/O 完成)。
- 你下楼取餐,然后吃饭(程序处理完成后的逻辑)。
异步 I/O 的特点
- 非阻塞:下单后你可以继续干其他事,不用站在厨房等饭做好。
- 并行处理:外卖小哥、商家、骑手可以同时工作(内核在后台高效处理 I/O)。
- 高效但复杂:你需要留手机号(回调函数)或设置提醒(信号),否则可能错过外卖。
Linux 中的异步 I/O 例子
io_uring
(Linux 5.1+ 推荐):- 像“美团外卖”一样高效,支持文件、网络等异步 I/O。
- 程序提交订单(
io_uring_prep_read
),然后继续执行其他任务,内核完成后通知(io_uring_wait_cqe
)。
libaio
(较旧):- 类似“老式外卖平台”,功能有限,但也能实现异步文件 I/O。
3. 同步 vs 异步:外卖 vs 做饭
对比项 | 同步 I/O(自己做饭) | 异步 I/O(点外卖) |
---|---|---|
是否阻塞 | 阻塞(必须亲自等) | 非阻塞(下单后可以继续玩) |
能否并行 | 不能(一步接一步) | 能(商家、骑手、你同时干活) |
效率 | 低(浪费时间等) | 高(充分利用时间) |
复杂度 | 简单(按步骤来) | 复杂(需要处理通知) |
适用场景 | 低并发、简单任务(如单线程脚本) | 高并发、高性能需求(如 Web 服务器) |
4. 实际代码类比
(1) 同步 I/O(自己做饭)
#include <unistd.h>
#include <stdio.h>int main() {char buf[1024];// 阻塞式读取文件(相当于站在菜市场等菜)ssize_t n = read(STDIN_FILENO, buf, sizeof(buf)); if (n > 0) {// 读取完成后才能继续处理(相当于做饭)write(STDOUT_FILENO, buf, n); }return 0;
}
- 问题:如果
read()
很慢(比如从硬盘读取大文件),程序会卡住,无法做其他事。
(2) 异步 I/O(点外卖)
#include <liburing.h>
#include <fcntl.h>
#include <stdio.h>void callback(struct io_uring_cqe *cqe) {printf("外卖已送达!读取了 %d 字节\n", cqe->res);
}int main() {struct io_uring ring;io_uring_queue_init(32, &ring, 0); // 初始化“外卖平台”int fd = open("test.txt", O_RDONLY);char buf[1024];// 提交异步读请求(相当于下单)struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);io_uring_prep_read(sqe, fd, buf, sizeof(buf), 0);io_uring_submit(&ring); // 告诉内核:“我点单了,你去做吧!”printf("下单成功!继续刷抖音...\n"); // 程序继续执行其他任务// 稍后检查外卖状态(相当于等手机通知)struct io_uring_cqe *cqe;io_uring_wait_cqe(&ring, &cqe);callback(cqe); // 处理完成事件(取外卖)io_uring_cqe_seen(&ring, cqe); // 标记“已取餐”io_uring_queue_exit(&ring);close(fd);return 0;
}
- 优势:提交请求后,程序可以继续做其他事,内核在后台高效处理 I/O。
5. 总结
- 同步 I/O(自己做饭):简单但低效,适合简单任务。
- 异步 I/O(点外卖):高效但复杂,适合高并发场景(如 Nginx、Redis、数据库)。
- Linux 推荐:
- 高性能文件 I/O:
io_uring
(现代外卖平台) - 网络 I/O:
epoll()
+ 非阻塞 I/O(类似“多线程做饭”)
- 高性能文件 I/O: