I/O 模型是操作系统、网络协议栈与上层应用在数据交互过程中所遵循的“协作方式”。 它决定了:
从业务层看,I/O 模型影响系统吞吐、延迟、并发上限。 从工程层看,I/O 模型决定框架选择、架构模式(Reactor/Proactor)及优化路径。 从本质层看,I/O 模型是如何协调 CPU(计算资源)、线程(执行单元)、内核(数据源)之间的等待关系。
I/O 模型不是“如何读写数据”,而是:
不同 I/O 模型的本质区别是: 等待阶段由谁承担?
最终目标: 减少线程浪费,提升 CPU 利用率,让等待更智能。
┌──────────────────────────────────────────────┐
│ 应用层(APP) │
└──────────────────────────────────────────────┘
▲ ▲
│ 回调/事件 │
│ │
┌──────────────────────────────────────────────┐
│ 内核态(Kernel Space) │
│ 网络协议栈 | 驱动 | 中断 | I/O 队列 │
└──────────────────────────────────────────────┘
I/O 模型差异 = 谁负责等待?
┌───────────────┐
│ CPU / 应用线程 │
└───────────────┘
│
┌─────────▼─────────┐
│ IO 抽象模型层 │
│ BIO / NIO / AIO │
└─────────┬─────────┘
┌──────────────────┼──────────────────┐
│ │ │
┌──────▼─────┐ ┌──────▼─────┐ ┌──────▼─────┐
│ 同步模型 │ │ 多路复用 │ │ 异步模型 │
└───────────┘ └───────────┘ └───────────┘
(底层对应 select/poll/epoll)
优点: 编程简单 缺点: 线程占用 = 连接数,无法扩展
优点: 不阻塞线程 缺点: 空轮询浪费 CPU,实用性差
优点: 高效、高并发、线程少 缺点: 存在事件回调开销与内核复杂性
主要实现方式
| 方法 | 核心机制 | 特点 | 局限性 |
|---|---|---|---|
| select | 用户空间传入 fd 集合,内核轮询并返回就绪集合 | 简单,跨平台好 | 文件描述符数量有限(通常 1024),每次调用都需拷贝 fd 集合 |
| poll | 以结构数组形式传递 fd 集合,支持大于 1024 个描述符 | 支持更多 fd | 每次调用仍需内核-用户空间拷贝,性能随 fd 数量增长下降 |
| epoll | 内核维护 fd 与事件映射表,用户态与内核态共享内存 | 高效,支持成千上万 fd,无重复拷贝 | Linux 专用,复杂度稍高 |
用户空间构造 fd 集合
系统调用进入内核
内核返回结果
应用遍历就绪 fd
性能问题:
int select(int __nfds, fd_set *__readfds, fd_set *__writefds, fd_set *__exceptfds, struct timeval *__timeout);
int poll(struct pollfd *__fds, nfds_t __nfds, int __timeout);
核心思想:由内核维护事件集合,应用只需“注册 fd 与事件”,就绪事件通过共享内存返回,无需每次遍历所有 fd。
epoll_create / epoll_create1
epoll_ctl
epoll_wait
性能优势:
int epoll_create(int size);
int epoll_create1(int flags);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
graph TB
A[应用程序] --调用--> B(epoll_wait)
B --返回就绪事件--> C[事件循环]
C --遍历就绪事件--> D{事件类型}
D --新连接事件--> E[建立新连接]
D --读事件--> F[读取数据]
D --写事件--> G[发送数据]
D --关闭事件--> H[关闭连接]
优点: 线程不阻塞 缺点: 信号难用、不可靠、跨平台差
优点: 真正的异步、高吞吐、低延迟 缺点: 实现复杂、平台差异大;IO_uring 才真正好用
| 模型 | 内核/应用负责内容 | 优势 | 适用场景 |
|---|---|---|---|
| 阻塞 | 应用等待 + 读写 | 简单 | 小业务、脚本 |
| 非阻塞 | 应用轮询 | 简单但低效 | 少 |
| 多路复用 | 内核等待、应用读写 | 并发强 | Web / IM / API 网关 |
| 信号驱动 | 内核信号通知 | 中等 | 少 |
| AIO | 内核完成全部流程 | 最强 | 高性能、IO 密集 |
关键区别: I/O 多路复用是事件驱动模型的基础;AIO 是最终形态。
映射到:
核心流程:
事件就绪 → Select 返回 → 应用层发起 read/write
本质:事件通知 + 应用负责 I/O
映射到:
核心流程:
应用提交IO → 内核执行IO → 回调通知完成
本质:内核负责全部 I/O 操作,回调通知应用处理结果
未来趋势:Proactor + io_uring 将成为主流。
| 场景 | 推荐模型 |
|---|---|
| 低并发脚本、CLI 工具 | Blocking I/O |
| 普通业务系统(<5w连接) | NIO / epoll |
| 高并发高吞吐(>10w连接) | Netty + epoll |
| 超高性能定制服务器 | io_uring (AIO) |
| Windows 服务 | IOCP |
| 语言 | I/O 模型 | 框架 |
|---|---|---|
| Java | NIO (epoll) | Netty |
| Go | epoll + M:N 调度 | Go Runtime |
| Node.js | libuv → epoll | Node |
| Rust | tokio → epoll | 高性能服务 |
| C++ | epoll / io_uring | Muduo, Boost.Asio |
趋势: 未来框架将逐步从 Reactor(epoll)向 Proactor(io_uring)演进。
最终形态:
“语言 Runtime + 异步内核 + 零拷贝通道” → 极致 I/O 并发
I/O 模型体系本质包含三点: