基于本文回答
0
评论

常见的 I/O 模型有哪些?(阻塞、非阻塞、多路复用、信号驱动、异步)

知识点图片

这是一个非常经典的计算机系统和网络编程面试题。根据 UNIX 网络编程(Richard Stevens 的经典著作)的分类,I/O 模型主要有 5 种。

为了理解这些模型,首先需要明确一个 I/O 操作(通常是 Read 操作)涉及的两个主要阶段

  1. 等待数据准备好 (Waiting for the data to be ready):数据从网络到达网卡,并被复制到内核缓冲区。
  2. 将数据从内核拷贝到用户空间 (Copying the data from the kernel to the process):内核将数据拷贝到应用程序的内存缓冲区。

以下是这 5 种模型的详细解析及生活类比(假设你在钓鱼):


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

这是最流行、最简单的模型。默认情况下,所有的 Socket 都是阻塞的。

  • 过程
    • 用户进程调用 recvfrom 系统调用。
    • 阶段 1:如果没有数据,进程进入睡眠状态(阻塞),直到数据到达。
    • 阶段 2:数据到达后,内核将其拷贝到用户内存,拷贝完成后,进程被唤醒。
  • 特点:进程在两个阶段都被阻塞
  • 钓鱼类比:你抛下鱼竿,眼睛死死盯着浮标,什么别的事都不做,直到鱼上钩并把你拉上来。

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

可以通过设置 Socket 使其变为非阻塞。

  • 过程
    • 用户进程调用 recvfrom
    • 阶段 1:如果数据没准备好,内核立即返回一个错误(EWOULDBLOCKEAGAIN),进程不睡眠。
    • 进程收到错误后,再次调用 recvfrom(轮询/Polling)。
    • 阶段 2:一旦数据准备好,进程再次发起调用时,内核开始拷贝数据。在拷贝数据的过程中,进程是阻塞的
  • 特点:进程通过轮询 (Polling) 方式检查数据,占用 CPU 较高。虽然阶段 1 不阻塞,但阶段 2 依然阻塞。
  • 钓鱼类比:你抛下鱼竿,手里拿着手机玩。每隔几秒钟抬头看一眼浮标。没鱼就继续玩手机,有鱼了就放下手机把鱼拉上来。

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

这是目前高并发网络编程的主流模型(如 Nginx, Redis, Netty)。常用的系统调用有 selectpollepoll

  • 过程
    • 用户进程调用 select(或 epoll),传入多个 Socket 描述符。
    • 阶段 1:进程在 select 调用上阻塞,等待多个 Socket 中的任意一个变为可读。
    • 一旦有一个或多个 Socket 数据准备好,select 返回。
    • 阶段 2:用户进程调用 recvfrom,将数据从内核拷贝到用户空间(此过程阻塞)。
  • 特点
    • 看起来和阻塞 I/O 很像,甚至还多了一次系统调用。但它的核心优势在于一个线程可以同时处理多个连接
    • 如果是处理单个连接,它的性能不如阻塞 I/O。
  • 钓鱼类比:你是个鱼塘管理员,同时看着 100 个鱼竿。你坐在监控室盯着报警器(Select),一旦某个鱼竿的报警器响了,你就跑过去把那条鱼拉上来。

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

  • 过程
    • 用户进程开启 Socket 的信号驱动 I/O 功能,并通过 sigaction 系统调用安装一个信号处理函数。
    • 阶段 1:系统调用立即返回,进程继续工作,不阻塞
    • 当数据准备好时,内核向进程发送 SIGIO 信号。
    • 阶段 2:进程在信号处理函数中调用 recvfrom,将数据拷贝到用户空间(此过程阻塞)。
  • 特点:阶段 1 是非阻塞的,但阶段 2(拷贝数据)依然是阻塞的。
  • 钓鱼类比:你在鱼竿上绑了一个铃铛,然后坐在一旁看书。铃铛响了(信号),你放下书,把鱼拉上来。

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

这是真正的异步模型。

  • 过程
    • 用户进程发起 aio_read 系统调用,传入缓冲区地址等信息。
    • 阶段 1 & 阶段 2:系统调用立即返回,进程继续做其他事。内核负责等待数据将数据拷贝到用户指定的缓冲区。
    • 所有操作完成(数据已经在用户缓冲区了),内核通过信号或回调函数通知用户进程。
  • 特点:进程在整个 I/O 过程中(等待 + 拷贝)完全没有被阻塞
  • 钓鱼类比:你雇了一个专业的捕鱼人。你告诉他:“去帮我钓条鱼,直接送到我家厨房的桶里,搞定后发微信告诉我。”然后你就去逛街了。

总结与对比

为了区分这些模型,最关键的是看同步 (Synchronous) 还是 异步 (Asynchronous)

根据 POSIX 的定义:

  • 同步 I/O 操作:导致请求进程阻塞,直到 I/O 操作完成。
  • 异步 I/O 操作:不导致请求进程阻塞。
模型 阶段 1 (等待数据) 阶段 2 (拷贝数据) 归类 关键点
阻塞 I/O 阻塞 阻塞 同步 傻等,啥也不干
非阻塞 I/O 非阻塞 (轮询) 阻塞 同步 忙轮询,浪费 CPU
多路复用 阻塞 (在 select 上) 阻塞 同步 一个看门大爷管所有门
信号驱动 非阻塞 (回调) 阻塞 同步 也就是等数据时不堵,读数据还得堵
异步 I/O 非阻塞 非阻塞 异步 全程托管,真正的甩手掌柜

注意:
前四种模型(阻塞、非阻塞、多路复用、信号驱动)实际上都属于 同步 I/O。因为它们在第二阶段(数据从内核拷贝到用户空间)时,进程都是阻塞的,必须等待拷贝完成才能继续执行。

只有 异步 I/O (AIO) 才是真正的异步,因为数据拷贝过程也是由内核完成的,用户进程完全没参与等待。

右滑查看面试常问