前些天略研究了一下node.js的异步原理,才对node 的处理异步机制有些了解,于是想着写篇博文,一是可以帮助正在学习的猿们,二是也可以巩固自己,如果本文有什么错误的地方,还请各位指出,我会加以改正。
windows 的IOCP
首先我得先简单的说一下windows下处理高并发的异步模型,它是一种通信模型,用来解决服务器高并发的一种技术,它就是IOCP(I/O Completion Port)IO完成端口。
node异步IO
在node的底层libuv库里实现了IOCP这个异步模型,当我们在windows下运行Node 的程序,对于异步的请求处理,内部会用线程池来实现,这个线程池会被windows 管理,而这种实现方式就是IOCP的实现方式。
说到异步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的异步请求处理实际上是由线程池来支持的,因此实际上的处理过程还是在工作线程,只是这个线程不对用户开放,而是由系统来接手管理。
非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观察者