五种 IO 模型与非阻塞 IO

五种 IO 模型与非阻塞 IO
一、IO 的本质与核心矛盾在计算机世界中IO输入 / 输出的本质是数据在内存与外设如网络、磁盘之间的拷贝。但 IO 过程并非只有 “拷贝”还包含 “等待”—— 等待数据准备就绪。实际场景中等待时间往往远大于拷贝时间。因此“高效 IO” 的核心是减少等待时间的占比。二、五种 IO 模型详解为解决 “等待耗时” 问题衍生出五种 IO 模型可类比 “钓鱼” 场景理解IO 模型类比场景核心特点阻塞 IO张三死等鱼上钩啥也不干数据未就绪时系统调用阻塞直到数据准备好才拷贝非阻塞 IO李四边看书边等鱼轮询数据未就绪时系统调用立即返回错误码EWOULDBLOCK需主动轮询检查信号驱动 IO王五挂铃铛响了再收杆内核数据就绪时通过SIGIO信号通知应用应用再发起 IO 操作IO 多路转接赵六同时看多个鱼竿select/poll同时监听多个文件描述符任一就绪就处理减少等待单个 IO 的时间占比异步 IO田七雇人钓鱼钓好直接送过来内核完成 “数据准备 拷贝” 后主动通知应用应用全程不参与等待与拷贝2.1 阻塞 IO最基础的 IO 模型流程应用发起read/recv系统调用内核检查数据是否就绪若未就绪进程被挂起阻塞数据就绪后内核将数据从内核空间拷贝到用户空间拷贝完成系统调用返回进程继续执行特点实现简单是套接字默认模式等待时进程完全阻塞无法执行其他任务2.2 非阻塞 IO流程应用将文件描述符设为非阻塞通过fcntl发起read/recv若数据未就绪立即返回EWOULDBLOCK错误应用需轮询反复调用检查数据是否就绪数据就绪后拷贝数据并返回特点等待时可执行其他任务但轮询会浪费 CPU 资源仅适用于特定场景如需要持续响应的服务2.3 信号驱动 IO流程应用通过sigaction注册SIGIO信号处理函数内核数据就绪时发送SIGIO信号给应用信号处理函数中发起read/recv拷贝数据特点无需主动轮询由内核通知但信号处理逻辑复杂且拷贝时进程仍会阻塞2.4 IO 多路转接select/poll/epoll流程应用调用select传入多个文件描述符内核监听所有描述符任一就绪则返回应用找到就绪的描述符发起read/recv拷贝数据特点可同时监听多个 IO 事件减少等待占比是高性能网络编程的核心技术如 Nginx、Redis2.5 异步 IO流程应用发起异步 IO如aio_read立即返回内核自动完成 “数据准备 拷贝”拷贝完成后通过信号 / 回调通知应用特点应用全程不参与等待与拷贝效率最高但实现复杂依赖操作系统支持如 Linux 的io_uring三、同步 vs 异步阻塞 vs 非阻塞3.1 同步与异步关注 “是否参与 IO 过程”同步应用需亲自参与 IO 的 “等待” 或 “拷贝”前四种 IO 模型异步应用仅发起 IO 请求内核完成所有操作后通知仅异步 IO3.2 阻塞与非阻塞关注 “等待时的进程状态”阻塞等待时进程被挂起无法执行其他任务非阻塞等待时进程可继续执行其他任务四、非阻塞 IO 的实现与实践4.1fcntl函数设置非阻塞属性#include fcntl.h #include unistd.h // 将文件描述符设为非阻塞 void SetNonBlock(int fd) { int flags fcntl(fd, F_GETFL); // 获取当前属性 if (flags 0) { perror(fcntl F_GETFL error); return; } fcntl(fd, F_SETFL, flags | O_NONBLOCK); // 添加非阻塞标志 }4.2 非阻塞 IO 的轮询示例#include iostream #include unistd.h #include fcntl.h #include string.h #include errno.h // 回调函数类型 typedef void (*TaskFunc)(); // 模拟其他任务 void TaskA() { std::cout 执行任务A\n; } void TaskB() { std::cout 执行任务B\n; } int main() { // 1. 设置标准输入为非阻塞 SetNonBlock(STDIN_FILENO); char buf[1024]; while (true) { // 2. 非阻塞读取标准输入 ssize_t n read(STDIN_FILENO, buf, sizeof(buf) - 1); if (n 0) { // 数据就绪处理输入 buf[n] \0; std::cout 输入内容: buf std::endl; } else if (n 0) { // 输入结束 std::cout 输入结束\n; break; } else { // 3. 处理错误区分未就绪和真错误 if (errno EAGAIN || errno EWOULDBLOCK) { // 数据未就绪执行其他任务 TaskA(); TaskB(); sleep(1); // 避免CPU空转 } else if (errno EINTR) { // 被信号中断重试 continue; } else { // 其他错误 perror(read error); break; } } } return 0; }运行效果无输入时程序不会阻塞而是执行TaskA和TaskB有输入时立即读取并处理输入内容五、总结IO 的核心矛盾是 “等待耗时”五种 IO 模型从不同角度优化等待过程阻塞 IO最简单但最不灵活非阻塞 IO可并行任务但轮询耗 CPU信号驱动 IO内核通知但处理复杂IO 多路转接同时监听多 IO高性能场景首选异步 IO全程无需参与效率最高但实现复杂非阻塞 IO 是理解 “IO 优化” 的基础通过fcntl可快速实现但实际场景中更多与 IO 多路转接结合使用以达到更高的效率。