Linux 系统下有 五种经典 IO 模型,这是网络编程和高并发服务器开发中必须掌握的核心知识(尤其在 Redis、Nginx、Netty 等场景中反复出现)。
2026 年视角下,io_uring 已成为异步 IO 的主流趋势,但面试和基础理解仍以这五种模型为主。
一、五种 IO 模型一览表(最清晰对比)
| 模型序号 | 模型名称 | 是否阻塞(进程视角) | 是否同步(数据拷贝阶段) | 内核是否帮忙拷贝到用户缓冲区 | 典型系统调用 | 适用场景 | 效率排序(1最高) |
|---|---|---|---|---|---|---|---|
| 1 | 阻塞式 IO | 是 | 是 | 是 | read / recv | 简单单连接、低并发 | ★☆☆☆☆ |
| 2 | 非阻塞式 IO | 否(轮询) | 是 | 是 | read + EAGAIN 轮询 | 极少数场景(几乎不用) | ★★☆☆☆ |
| 3 | IO 多路复用 | 是(select/poll/epoll) | 是 | 是 | select / poll / epoll | 高并发网络服务器(主流) | ★★★★☆ |
| 4 | 信号驱动式 IO | 否 | 是 | 是 | sigaction + SIGIO | 极少用(历史遗留) | ★★★☆☆ |
| 5 | 异步 IO(AIO / io_uring) | 否 | 否 | 是(内核完成拷贝) | aio_read / io_uring_submit | 极致性能、高吞吐磁盘/网络 | ★★★★★ |
关键区分点(面试最常问):
- 阻塞 vs 非阻塞:调用 read/recv 时,数据没准备好,进程是否挂起(阻塞态)
- 同步 vs 异步:数据从内核缓冲区 → 用户缓冲区的拷贝阶段,谁负责?(同步=用户进程负责拷贝,异步=内核负责拷贝)
→ 前四种都是同步 IO(用户进程要自己把数据从内核 copy 到用户空间)
→ 第五种才是真正的异步 IO(内核全部搞定,包括拷贝,用户进程只收通知)
二、非阻塞 IO 详细讲解(最容易混淆的部分)
非阻塞 IO 的本质:
把 socket / 文件描述符 设置为 O_NONBLOCK 后,read/recv 等调用不会让进程睡眠,而是立刻返回。
- 数据没准备好 → 返回 -1 + errno = EAGAIN / EWOULDBLOCK
- 数据准备好 → 正常返回读取的字节数
代码示例(最经典写法)
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
// 设置非阻塞
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
// 循环读取(轮询)
while (1) {
ssize_t n = read(fd, buf, sizeof(buf));
if (n > 0) {
// 读到数据,处理
} else if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 没数据,继续轮询(或 sleep(小) / 其他业务)
// 这里就是 CPU 空转的代价!
} else {
// 真正错误
perror("read error");
break;
}
} else {
// n == 0,对端关闭
break;
}
}
非阻塞 IO 的致命缺点(为什么生产几乎不用纯非阻塞):
- CPU 空转:轮询消耗 100% CPU(尤其多连接时灾难)
- 无法处理大量连接:每个 fd 都需要一个循环轮询
- 业务逻辑复杂:代码全是嵌套的 while + errno 判断,可读性极差
结论:
纯非阻塞 IO 几乎只用于极少数特殊场景(如单连接、已知数据很快到达)。
高并发场景必须结合 IO 多路复用(select/poll/epoll)使用。
三、五种模型流程对比图(文字版)
1. 阻塞 IO
用户进程 → read() → 阻塞等待 → 数据就绪 → 内核拷贝到用户缓冲区 → 返回
2. 非阻塞 IO
用户进程 → read() → 立刻返回 EAGAIN → 轮询 → ... → 数据就绪 → read()成功 → 拷贝 → 返回
3. IO 多路复用(以 epoll 为例)
用户进程 → epoll_wait() → 阻塞等待事件 → 内核通知哪些 fd 就绪 → 用户 read() → 拷贝 → 返回
4. 信号驱动 IO
用户进程 → sigaction(SIGIO) → 继续干活 → 数据就绪 → 内核发信号 → 信号处理函数 read() → 拷贝
5. 异步 IO(io_uring / POSIX AIO)
用户进程 → io_submit() → 立刻返回 → 继续干活 → 内核完成全部(读+拷贝) → 通知(或轮询完成队列)
四、2026 年生产视角总结(面试 + 实际选型)
| 场景 | 推荐模型 | 主流实现 | 备注(2026) |
|---|---|---|---|
| 简单单线程阻塞服务器 | 阻塞 IO | read/accept | 教学用,生产已淘汰 |
| 高并发网络服务器 | IO 多路复用 | epoll(Linux) / kqueue(BSD/macOS) | Nginx、Redis、Netty 默认 |
| 极致性能网络 + 磁盘 | 异步 IO | io_uring | Redis 7+、Nginx 实验支持、liburing |
| 历史遗留或特殊信号场景 | 信号驱动 IO | SIGIO | 几乎不用,容易丢事件 |
| 轮询单个 fd(很少见) | 非阻塞 IO | O_NONBLOCK + while | 仅作理解,生产不推荐 |
一句话记忆口诀(超好背):
阻塞傻等鱼上钩,非阻塞自己一直问,多路复用请渔夫,信号驱动鱼咬钩喊你,异步渔夫全包办。
如果你想看具体代码实现(C / Python epoll / io_uring 入门),或者想对比 select/poll/epoll 的性能差异,告诉我,我可以继续展开。