Administrator
发布于 2025-09-22 / 156 阅读
0

对于io分为哪几个类型

1. 阻塞式 I/O (Blocking I/O)

这是最简单、最常见的模型。

  • 核心流程

    1. 应用程序发起一个 I/O 系统调用(如read)。

    2. 内核开始准备数据(例如,等待网络数据包到达)。

    3. 在数据准备好之前,应用程序的进程 / 线程会被挂起(Blocked),进入睡眠状态,不消耗 CPU 资源。

    4. 当数据准备好后,内核将数据从内核空间复制到用户空间。

    5. read调用返回,应用程序被唤醒,继续执行。

  • 特点

    • 简单直观:编程模型简单,符合直觉。

    • CPU 效率高:在等待 I/O 时,线程不消耗 CPU。

    • 并发能力差:如果要同时处理多个 I/O 请求,通常需要为每个请求创建一个新线程。这会导致线程爆炸,消耗大量内存和系统资源。

  • 适用场景

    • 对并发要求不高的简单应用,如命令行工具、简单的服务器。


2. 非阻塞式 I/O (Non-blocking I/O)

应用程序在等待 I/O 时不会被挂起,可以去做其他事情。

  • 核心流程

    1. 首先需要将文件描述符(fd)设置为非阻塞模式。

    2. 应用程序发起read系统调用。

    3. 如果数据未准备好,内核会立即返回一个错误(如EAGAINEWOULDBLOCK),而不是阻塞线程。

    4. 应用程序收到错误后,知道 I/O 还没完成,可以去执行其他任务。

    5. 应用程序需要通过 轮询(Polling) 的方式,不断调用read来检查数据是否已准备好。

    6. 一旦数据准备好,read调用会成功返回数据。

  • 特点

    • 并发能力强:一个线程可以 “管理” 多个 I/O 通道。

    • CPU 效率低:如果轮询频率过高,会导致 CPU 在没有数据时也被频繁占用,造成 “忙等”,浪费资源。

  • 适用场景

    • 对响应时间要求极高,且 I/O 操作非常快的场景。通常不单独使用,而是作为其他高级模型(如多路复用)的基础。


3. 多路复用 I/O (I/O Multiplexing)

这是高性能网络编程的基石,如select, poll, epoll (Linux) 和 kqueue (BSD)。

  • 核心流程

    1. 应用程序创建一个I/O 多路复用器(如epoll实例)。

    2. 将需要监控的多个文件描述符(fd)注册到这个复用器上,并告诉它要监控的事件类型(如 “可读”、“可写”)。

    3. 应用程序调用epoll_wait系统调用。

    4. 内核会监控所有注册的 fd。在没有任何一个 fd 就绪时,epoll_wait阻塞应用程序线程。

    5. 至少有一个 fd 就绪时(例如,数据到达),epoll_wait调用返回,返回一个包含所有就绪 fd 的列表。

    6. 应用程序然后可以遍历这个就绪列表,对每个就绪的 fd 执行read操作(此时read几乎是立即返回的,因为数据已准备好)。

  • 特点

    • 高效:一个线程可以高效地管理成百上千个 I/O 连接。

    • 事件驱动:避免了非阻塞式 I/O 的 “忙等”,只有当事件发生时才会唤醒线程处理。

    • “伪并发”:虽然是单线程,但通过高效的事件分发机制,实现了很高的并发处理能力。

  • 适用场景

    • 需要同时处理大量并发连接的服务器应用,如 Nginx、Redis、Node.js 的底层。


4. 信号驱动 I/O (Signal-Driven I/O)

这种模型通过信号来通知 I/O 事件,不常用。

  • 核心流程

    1. 应用程序为 SIGIO 信号设置一个信号处理函数

    2. 开启一个 fd 的信号驱动 I/O 功能,并告诉内核当该 fd 上有事件发生时,发送一个 SIGIO 信号给进程。

    3. 应用程序可以继续执行其他任务,不会被阻塞

    4. 当 fd 上的数据准备好时,内核会向应用程序进程发送一个 SIGIO 信号。

    5. 进程捕获到信号后,在信号处理函数中调用read来读取数据。

  • 特点

    • 异步通知:应用程序可以在 I/O 准备期间做其他事,无需轮询。

    • 实现复杂:信号处理在多线程 / 多进程环境下非常复杂,容易出错,且信号队列容量有限,可能丢失事件。

  • 适用场景

    • 非常特定的场景,如高性能的网络嗅探器。在通用服务器编程中很少使用。


5. 异步 I/O (Asynchronous I/O)

这是最强大、最理想的 I/O 模型,如POSIX AIO, IOCP (Windows)。

  • 核心流程

    1. 应用程序调用一个异步 I/O 函数(如aio_read),并传入一个回调函数的指针。

    2. 内核立即返回,不会阻塞应用程序线程。应用程序可以继续执行。

    3. 内核在后台完成所有工作:等待数据准备好,然后将数据从内核空间复制到用户空间。

    4. 所有操作都完成后,内核会通知应用程序。通知方式可以是:

      • 发送一个信号。

      • 唤醒一个等待的线程。

      • 调用用户提供的回调函数。

    5. 应用程序在收到通知后,即可使用已经准备好的数据。

  • 特点

    • 真正的异步:将整个 I/O 操作(等待 + 复制)都交给了内核,应用程序在 I/O 完成前完全无感。

    • 编程模型复杂:需要处理回调、状态管理等,逻辑相对复杂。

    • 性能潜力最高:能最大化 CPU 和 I/O 设备的利用率。

  • 适用场景

    • 对性能要求极致,且 I/O 操作耗时较长的场景,如数据库系统、高性能文件服务器。


核心区别总结

模型

等待数据阶段

数据复制阶段

核心优点

核心缺点

关键系统调用

阻塞式 I/O

阻塞

阻塞

简单

并发能力差

read, write

非阻塞式 I/O

不阻塞

阻塞

并发能力强

CPU 忙等,效率低

read (循环调用)

多路复用 I/O

阻塞 (在复用器上)

阻塞

高效,一个线程管理多连接

编程模型较复杂

select, poll, epoll

信号驱动 I/O

不阻塞

阻塞

异步通知,无需轮询

实现复杂,信号易丢失

sigaction, fcntl

异步 I/O

不阻塞

不阻塞

性能最高,真正异步

编程模型最复杂

aio_read, IOCP

关键区分点

  • 阻塞 vs 非阻塞:关注的是等待数据准备这个阶段,线程是否被挂起。

  • 同步 vs 异步:关注的是整个 I/O 操作(等待数据 + 复制数据)是否需要应用程序亲自参与。

    • 同步 I/O(前四种):应用程序需要至少参与 “数据复制” 这个阶段。

    • 异步 I/O(第五种):应用程序将整个 I/O 操作全权委托给内核,只关心最终结果。