node.js 事件循环 异步IO 和 非IO异步

时间:2021-10-16 17:58:03

前些天略研究了一下node.js的异步原理,才对node 的处理异步机制有些了解,于是想着写篇博文,一是可以帮助正在学习的猿们,二是也可以巩固自己,如果本文有什么错误的地方,还请各位指出,我会加以改正。

windows 的IOCP

首先我得先简单的说一下windows下处理高并发的异步模型,它是一种通信模型,用来解决服务器高并发的一种技术,它就是IOCP(I/O Completion Port)IO完成端口。

node异步IO

在node的底层libuv库里实现了IOCP这个异步模型,当我们在windows下运行Node 的程序,对于异步的请求处理,内部会用线程池来实现,这个线程池会被windows 管理,而这种实现方式就是IOCP的实现方式。

说到异步IO我们得先看一下是事件循环,它是整个异步实现的核心,它不断的从观察者把事件取出,分离回调来执行。
node.js 事件循环 异步IO 和 非IO异步

整个绿色的区域就是一个事件循环,它的作用就在于:
①取出事件
②执行相关联的回调

如果我们要执行一次异步IO,那它的整个流程是怎样的呢,比如我要执行一次读取文件的操作,这里我用promise来实现。

const fs = require("fs");
let readFile = path => {
    return new Promise((resolve,reject) => { fs.readFile(path,"utf-8",(err,data) => { if(err){ reject(err); } else{ resolve(data); } }); }); }; readFile("./data.txt") .then(data => { console.log(data); }) .catch(err => { console.log("err"); });

那么它大概经历了以下的几个流程
node.js 事件循环 异步IO 和 非IO异步

可以看出,node的异步请求处理实际上是由线程池来支持的,因此实际上的处理过程还是在工作线程,只是这个线程不对用户开放,而是由系统来接手管理。

非IO异步

现在来看一下非IO异步操作,比如说setTimeout(),setInterval(),setImmdiate()和process.nextTick(),前面文章我已经说过nextTick比setTimeout要快的多,那么为什么呢?

我们可以先来验证这个结论

process.nextTick(() => { console.log("nextTick done"); }); setTimeout(() => { console.log("setTimeout done"); },0);

输出:

nextTick done
setTimeout done

如果我们将两个执行顺序对调一下,我们会发现结果还是一样的。

setTimeout(() => { console.log("setTimeout done"); },0); process.nextTick(() => { console.log("nextTick done"); }); 

输出:

nextTick done
setTimeout done

但是为什么nextTick 的速度要比setTimeout快的多呢。
这是因为setTimeout或者setInterval创建的定时器会被插入到观察者内部的一个红黑树中,但是setTimeout 或 setInterval的定时器都是不精确的,假如我们延迟了10s,但在9s的时候突然被一个延时操作占用了8秒,那么这次延时就不只10s了。

有些时候我们仅仅是想让一个函数异步执行,而不是想让它延迟多少秒后执行,这时候我们可以用process.nextTick(),因为相对于setTimeout来说,process.nextTick()更加轻量,不用花费去创建一个红黑树那么大的开销,它的内部是由一个队列直接来支持的,因此比setTimeout要快很多。

下面我们看一下setImmediate() 这个函数也是让传入的回调函数延迟执行,和process.nextTick()函数的作用相同,但是它们还是有实际的差异的,比如我们运行下面的代码:

setImmediate(() => { console.log("setImmediate done"); }); process.nextTick(() => { console.log("nextTick done"); });

输出 :

nextTick done
setImmediate done

在调换书写顺序时,结果还是一样的

process.nextTick(() => { console.log("nextTick done"); }); setImmediate(() => { console.log("setImmediate done"); });

输出 :

nextTick done
setImmediate done

从这里可以看出process.nextTick也要比setImmediate快,这是因为process.nextTick 属于 idle 观察者,而setImmediate属于 check观察者,从优先级来看,idle 观察者要 > check观察者 > IO观察者