编程与 AI16 分钟阅读更新于 2026-08-04

Linux I/O 模型怎么区分:阻塞、非阻塞、多路复用与异步

比较阻塞式 I/O、非阻塞式 I/O、I/O 多路复用、信号驱动 I/O 和异步 I/O,重点区分数据准备阶段与数据复制阶段。

相关工具

先把一次 I/O 拆成两个阶段

网络 I/O 或磁盘 I/O 看起来像一个动作,实际上通常包含两个阶段。第一阶段是等待数据准备好,例如网络数据报到达内核缓冲区;第二阶段是把数据从内核缓冲区复制到进程自己的用户空间缓冲区。

不同 I/O 模型的主要差异,不只是“会不会阻塞”,还在于进程等待的是哪一个阶段、谁负责通知阶段完成,以及数据复制发生时进程是否仍然需要参与。先分清这两个阶段,再比较各种模型,就不容易把非阻塞、多路复用和异步 I/O 混成一件事。

I/O 从数据准备到内核复制到用户缓冲区两个阶段的流程图
一次 I/O 的两个阶段

数据先在内核侧准备,再复制到进程缓冲区;不同模型改变的是等待和通知方式。

阻塞式 I/O:等待条件满足再继续

阻塞式 I/O 是最直观的方式。进程调用读取接口后,如果内核还没有准备好数据,调用就停在原地,线程进入等待状态;当数据准备完成并复制到用户空间后,调用返回,进程继续执行下一步。

等待期间线程通常不会持续占用 CPU,而是把处理器让给其他可运行任务。它的好处是程序结构简单,调用结果和控制流程比较自然;代价是一个线程被某个慢 I/O 占住时,不能同时推进其他独立连接,除非使用更多线程或进程。

非阻塞式 I/O:先问一次,暂时没有就返回

非阻塞式 I/O 不会让进程一直停在读取调用上。数据尚未准备好时,内核立即返回一个表示暂时不可用的结果,进程可以去做其他工作,稍后再尝试。数据准备好后,读取操作才真正把内容复制到用户缓冲区。

如果程序用紧密循环不断重试,就会变成轮询,消耗大量 CPU。非阻塞本身只解决“调用不被卡住”,并没有自动解决“什么时候再来尝试”。通常需要配合定时策略、I/O 多路复用或事件循环,才能避免空转。

阻塞式和非阻塞式 I/O 在时间线上等待方式对比图
阻塞与非阻塞的等待差异

阻塞调用停在内核等待,非阻塞调用立即返回,程序需要安排后续检查。

I/O 多路复用:把等待集中到一个入口

I/O 多路复用通过 select、poll 或 epoll 同时监视多个文件描述符。进程阻塞在多路复用调用上,而不是阻塞在某一个具体读取调用上;当任意一个描述符变为可读或可写时,多路复用调用返回,进程再对就绪描述符执行真正的读写。

它适合连接数量多、同时活跃连接较少的场景。一个线程可以等待许多对象,减少为每个连接单独创建线程的需要。但数据复制仍然需要由进程调用读取或写入完成,因此多路复用属于同步 I/O 范畴,不能等同于内核完成全部数据传输后再通知的异步 I/O。

信号驱动 I/O:由信号提醒可以开始读取

信号驱动 I/O 先为套接字安装 SIGIO 处理方式,随后读取调用可以立即返回。等数据报准备好后,内核向进程发送 SIGIO,信号处理函数或主循环据此知道现在值得进行读取。

这种模型把“数据已经准备好”的通知从主动查询变成事件提醒,等待期间进程可以继续工作。不过信号处理存在异步上下文限制,多个事件的合并、信号丢失后的状态确认以及处理函数的复杂度,都需要谨慎设计。

异步 I/O:提交后等待完成通知

异步 I/O 的关键是把整个操作交给内核。进程提交读取请求时,同时提供描述符、用户缓冲区、长度和偏移等信息,然后立即去做其他工作。内核负责等待数据准备并完成向用户空间的复制,操作真正完成后再通知进程。

它与信号驱动 I/O 的区别在通知时机:信号驱动 I/O 主要告诉进程“现在可以开始读取”,异步 I/O 告诉进程“这次读取已经完成”。这两个时机看起来接近,实际却决定了数据复制由谁发起、进程何时能够直接使用结果。

阻塞、非阻塞、多路复用、信号驱动和异步 I/O 五种模型对比图
五种 I/O 模型的等待位置

阻塞、非阻塞、多路复用、信号驱动和异步 I/O 在等待阶段与数据复制阶段承担的工作不同。

同步与异步到底在说什么

同步 I/O 强调调用方需要参与完成数据读取:数据准备好后,进程发起的读取操作还要把数据复制到用户空间,调用才能完成。阻塞、非阻塞和多路复用虽然等待方式不同,但都属于这种同步关系。

异步 I/O 则把等待和数据复制都交给内核,调用方在提交请求后不必参与中间过程,完成时再读取结果状态。这里的同步与异步不是“是否创建线程”的同义词,而是描述 I/O 操作完成责任和通知时机。

为什么轮询容易浪费 CPU

非阻塞 I/O 如果配合无限循环检查,就会形成轮询:程序不停调用读取接口,反复得到“暂时没有数据”,直到某一次终于成功。等待期间没有有效工作,但 CPU 仍在执行检查指令。

轮询可以提供较直接的响应路径,却要用计算资源换取低等待延迟。实际系统通常会根据场景选择事件通知、定时等待或多路复用,让线程在没有事件时休眠,事件到来后再被唤醒。延迟和 CPU 使用率之间没有脱离场景的绝对答案。

怎样根据场景选择 I/O 模型

程序简单、并发较低时,阻塞式 I/O 便于理解和维护;需要在一个线程中管理多个连接时,可以使用 I/O 多路复用;需要被动接收数据准备通知时,可以考虑信号驱动方式;确实希望由系统完成整个操作并在完成后通知时,才适合讨论异步 I/O。

选择之前还要看设备类型、平台支持、数据量、超时要求和团队维护能力。模型名称不是性能保证,错误处理、缓冲区管理、连接数量、业务计算和内核接口实现同样会影响结果。先明确等待目标,再决定机制,通常比先追逐某个术语更可靠。

用一条主线记住五种 I/O 模型

阻塞式 I/O 让进程等待数据和复制完成;非阻塞式 I/O 让调用立即返回,进程自行安排重试;I/O 多路复用让进程等待多个描述符的就绪状态;信号驱动 I/O 用信号提醒可以开始读取;异步 I/O 则由内核完成整个操作后通知结果。

它们都在处理同一件事:外部设备和网络数据比 CPU 慢,程序需要决定怎样等待、怎样被通知、谁来完成复制。只要抓住“数据何时准备好、数据何时进入用户缓冲、调用方何时继续工作”这三个问题,五种模型的差异就能自然展开。

常见问题

非阻塞 I/O 就是异步 I/O 吗?

不是。非阻塞只表示调用暂时不能完成时立即返回,数据准备好后仍需要程序发起读取;异步 I/O 则由内核完成整个操作后通知结果。

I/O 多路复用是不是让数据自动复制到用户空间?

不是。多路复用只报告哪些描述符已经就绪,程序仍要调用读取或写入接口完成数据交换。

为什么阻塞式 I/O 也可以支持并发?

可以通过多个线程或进程分别等待不同连接实现,但线程和进程数量、上下文切换以及共享状态管理会带来额外成本。