1. 阻塞式 I/O (Blocking I/O)
这是最简单、最常见的模型。
核心流程:
应用程序发起一个 I/O 系统调用(如
read)。内核开始准备数据(例如,等待网络数据包到达)。
在数据准备好之前,应用程序的进程 / 线程会被挂起(Blocked),进入睡眠状态,不消耗 CPU 资源。
当数据准备好后,内核将数据从内核空间复制到用户空间。
read调用返回,应用程序被唤醒,继续执行。
特点:
简单直观:编程模型简单,符合直觉。
CPU 效率高:在等待 I/O 时,线程不消耗 CPU。
并发能力差:如果要同时处理多个 I/O 请求,通常需要为每个请求创建一个新线程。这会导致线程爆炸,消耗大量内存和系统资源。
适用场景:
对并发要求不高的简单应用,如命令行工具、简单的服务器。
2. 非阻塞式 I/O (Non-blocking I/O)
应用程序在等待 I/O 时不会被挂起,可以去做其他事情。
核心流程:
首先需要将文件描述符(fd)设置为非阻塞模式。
应用程序发起
read系统调用。如果数据未准备好,内核会立即返回一个错误(如
EAGAIN或EWOULDBLOCK),而不是阻塞线程。应用程序收到错误后,知道 I/O 还没完成,可以去执行其他任务。
应用程序需要通过 轮询(Polling) 的方式,不断调用
read来检查数据是否已准备好。一旦数据准备好,
read调用会成功返回数据。
特点:
并发能力强:一个线程可以 “管理” 多个 I/O 通道。
CPU 效率低:如果轮询频率过高,会导致 CPU 在没有数据时也被频繁占用,造成 “忙等”,浪费资源。
适用场景:
对响应时间要求极高,且 I/O 操作非常快的场景。通常不单独使用,而是作为其他高级模型(如多路复用)的基础。
3. 多路复用 I/O (I/O Multiplexing)
这是高性能网络编程的基石,如select, poll, epoll (Linux) 和 kqueue (BSD)。
核心流程:
应用程序创建一个I/O 多路复用器(如
epoll实例)。将需要监控的多个文件描述符(fd)注册到这个复用器上,并告诉它要监控的事件类型(如 “可读”、“可写”)。
应用程序调用
epoll_wait系统调用。内核会监控所有注册的 fd。在没有任何一个 fd 就绪时,
epoll_wait会阻塞应用程序线程。当至少有一个 fd 就绪时(例如,数据到达),
epoll_wait调用返回,返回一个包含所有就绪 fd 的列表。应用程序然后可以遍历这个就绪列表,对每个就绪的 fd 执行
read操作(此时read几乎是立即返回的,因为数据已准备好)。
特点:
高效:一个线程可以高效地管理成百上千个 I/O 连接。
事件驱动:避免了非阻塞式 I/O 的 “忙等”,只有当事件发生时才会唤醒线程处理。
“伪并发”:虽然是单线程,但通过高效的事件分发机制,实现了很高的并发处理能力。
适用场景:
需要同时处理大量并发连接的服务器应用,如 Nginx、Redis、Node.js 的底层。
4. 信号驱动 I/O (Signal-Driven I/O)
这种模型通过信号来通知 I/O 事件,不常用。
核心流程:
应用程序为 SIGIO 信号设置一个信号处理函数。
开启一个 fd 的信号驱动 I/O 功能,并告诉内核当该 fd 上有事件发生时,发送一个 SIGIO 信号给进程。
应用程序可以继续执行其他任务,不会被阻塞。
当 fd 上的数据准备好时,内核会向应用程序进程发送一个 SIGIO 信号。
进程捕获到信号后,在信号处理函数中调用
read来读取数据。
特点:
异步通知:应用程序可以在 I/O 准备期间做其他事,无需轮询。
实现复杂:信号处理在多线程 / 多进程环境下非常复杂,容易出错,且信号队列容量有限,可能丢失事件。
适用场景:
非常特定的场景,如高性能的网络嗅探器。在通用服务器编程中很少使用。
5. 异步 I/O (Asynchronous I/O)
这是最强大、最理想的 I/O 模型,如POSIX AIO, IOCP (Windows)。
核心流程:
应用程序调用一个异步 I/O 函数(如
aio_read),并传入一个回调函数的指针。内核立即返回,不会阻塞应用程序线程。应用程序可以继续执行。
内核在后台完成所有工作:等待数据准备好,然后将数据从内核空间复制到用户空间。
当所有操作都完成后,内核会通知应用程序。通知方式可以是:
发送一个信号。
唤醒一个等待的线程。
调用用户提供的回调函数。
应用程序在收到通知后,即可使用已经准备好的数据。
特点:
真正的异步:将整个 I/O 操作(等待 + 复制)都交给了内核,应用程序在 I/O 完成前完全无感。
编程模型复杂:需要处理回调、状态管理等,逻辑相对复杂。
性能潜力最高:能最大化 CPU 和 I/O 设备的利用率。
适用场景:
对性能要求极致,且 I/O 操作耗时较长的场景,如数据库系统、高性能文件服务器。
核心区别总结
关键区分点:
阻塞 vs 非阻塞:关注的是等待数据准备这个阶段,线程是否被挂起。
同步 vs 异步:关注的是整个 I/O 操作(等待数据 + 复制数据)是否需要应用程序亲自参与。
同步 I/O(前四种):应用程序需要至少参与 “数据复制” 这个阶段。
异步 I/O(第五种):应用程序将整个 I/O 操作全权委托给内核,只关心最终结果。