今天闲暇看 Node.js,看到这些概念,有些困惑的地方,Google 一番,做些笔记。 因为术业方向问题,可能理解有误,但记录下来便于日后复习的时候比对。

阻塞 IO (Blocking IO), 非阻塞 IO (Non-Blocking IO)

核心区别在于,发起调用的进程(或线程)是否会挂起等待 IO 完成。

下面举例说明用户进程发起 一个 read 数据的阻塞 IO 操作的过程。 进程在发起系统调用后,就会一直干等。

  1. 首先等待 kernel 准备数据(如等待完整的网络数据包),这个阶段是 Blocking 的。
  2. 数据准备完毕后,kernel 将数据从缓冲区拷贝到用户空间,这个阶段也是 Blocking 的。
  3. 调用返回,用户进程也就解除了阻塞状态。

由上述过程可见,阻塞 IO 操作的流程很简单易懂,但也可以遇见,如果 IO 操作非常费时,那么用户进程在这个过程将会无法响应其他操作,这对某些应用来说,是无法接受的。

那么,一个非阻塞的 IO 又是怎么样的呢?

  1. 一个read的非阻塞IO,不会挂起用户进程,在 kernel 准备数据中的时候,收到系统调用后,会立即返回一个错误状态,用户进程不会被挂起,是Non-Blocking的。
  2. 发起调用的进程因为马上就能获得数据的准备的状态,因此可以周期性继续发起调用,直至 kernel 准备好数据。
  3. 当 kernel 准备好数据后,再接收到读取的调用的话,就会将数据拷贝到用户空间,这个阶段是Blocking的。
  4. 调用返回,圆满结束。

由上述过程可见,非阻塞 IO 操作的流程相比复杂一些,但因为用户进程不会被挂起,因此有机会在 IO 过程做其他事情,灵活很多。

同步 IO

通常情况下, 不论阻塞 IO 还是非阻塞 IO, 均是同步 IO。

异步 IO

着重说明下异步 IO, 因为这是我们 JavaScript 开发者接触最多的一个概念。

一个异步的 IO,发起调用户后会立即返回,可以不必等待 IO 结果去做其他事情,当有结果之后,会收到一个信号通知,从而可以回头进入数据处理环节。

具体的流程如下:

  1. 发起异步 IO,立即返回,可以做其他事情
  2. kernel 会默默地准备数据、拷贝数据到内存用户空间,向用户进程发一个 signal 通知其IO已完成
  3. 调用发起者处理 IO 结果

由此可见,一个异步的 IO 全程不阻塞,也不必关心 IO 的过程状态,省心省事。