对异步 IO 的理解

远子 •  2021年11月01日

为什么 Node 使用于 IO 密集型应用?

Node 利用单线程,原理多线程的死锁、状态同步的问题;利用异步 IO,让单线程远离阻塞,以更好的利用 CPU;单线程无法利用多核 CPU 的问题,Node 通过 child_progress 解决。

为什么要异步 IO?

操作系统对于 IO 只有两种方式:同步 IO 和异步 IO.

CPU 非常快,同步 IO 的方式会造成 CPU 等待 IO 完成,浪费时间。

有几种经典的服务器模型:

  1. 同步式。对于同步式的服务,一次只能处理一个请求,并且其余请求都处于等待状态。
  2. 每进程/每请求。为每个请求启动一个进程,这样可以处理多个请求,但是它不具备扩展性,因为系统资源只有那么多。
  3. 每线程/每请求。为每个请求启动一个线程来处理。尽管线程比进程要轻量,但是由于每个线程都占用一定内存,当大并发请求到来时,内存将会很快用光,导致服务器缓慢。每线程/每请求的扩展性比每进程/每请求的方式要好,但对于大型站点而言依然不够。
  4. 异步 IO。

异步 IO 的优势是 hold 住所有的请求,让 CPU 发挥最大的能力。

Node 的异步 IO 是如何实现的?

答案是事件驱动。

在进程启动时,Node 便会创建一个类似 while(true) 的循环,每执行一次循环的过程称为 Tick,每个 Tick 的过程就是查看是否有时间待处理,如果有,就取出事件及其回调。如果存在关联的函数,就执行他们。然后进入下个循环,直到没有事件时退出进程。

这个过程的逻辑十分简单,好比现实中的公交车,公交车在始发站和终点站之间重复的跑,有人招手时停车让乘客上车,隔一段时间等到乘客到站时,让他下车。

第一步:Node 的 libuv 一股脑儿接收所有的 IO 请求

第二步:Node 创建类似 while(true) 的循环,每执行一次循环称为 Tick

第三步:处理请求后,执行回调

第四步:所有请求处理结束后,退出循环

闭包:函数内返回函数,外部函数可以访问内部函数的局部变量

闭包是高阶函数的特性——函数可以作为参数或者返回值

闭包的问题在于,一旦创建,无法被垃圾回收,内存占用

无法立即回收的内存有:闭包和全局变量

(完)