node 中的异步

Node在*nix平台下采用了libeio配合libev实现I/O部分,实现了异步I/O
在Node v0.9.3中,自行实现了线程池来完成异步I/O
libeio 实质上依然是采用线程池与阻塞I/O模拟异步I/O
Node在Windows下采用IOCP来实现异步I/O
在这里插入图片描述
完成整个异步I/O环节的有事件循环、观察者 和 请求对象等。

事件循环 

白话讲解:

这个过程就如同饭馆的厨房,厨房一轮一轮地制作菜肴,但是要具体制作哪些菜肴取决于收 银台收到的客人的下单。厨房每做完一轮菜肴,就去问收银台的小妹,接下来有没有要做的菜, 如果没有的话,就下班打烊了。在这个过程中,收银台的小妹就是观察者,她收到的客人点单就 是关联的回调函数。当然,如果饭馆经营有方,它可能有多个收银员,就如同事件循环中有多个 观察者一样。收到下单就是一个事件,一个观察者里可能有多个事件。

在进程启动时,Node便会创建一个类似于while(true)的循环,每执行一次循环体的过程我 们称为Tick。每个Tick的过程就是查看是否有事件待处理,如果有,就取出事件及其相关的回调 函数。如果存在关联的回调函数,就执行它们。然后进入下个循环,如果不再有事件处理,就退 出进程。流程图如图
在这里插入图片描述
观察者
在每个Tick的过程中,如何判断是否有事件需要处理呢?这里必须要引入的概念是观察者
每个事件循环中有一个或者多个观察者,而判断是否有事件要处理的过程就是向这些观察者询问 是否有要处理的事件。
浏览器采用了类似的机制。事件可能来自用户的点击或者加载某些文件时产生,而这些产生 的事件都有对应的观察者
在Node中,事件主要来源于网络请求、文件I/O等,这些事件对应的 观察者有文件I/O观察者、网络I/O观察者等。观察者将事件进行了分类

事件循环是一个典型的生产者/消费者模型

异步I/O、网络请求等则是事件的生产者,源源 不断为Node提供不同类型的事件,这些事件被传递到对应的观察者那里,事件循环则从观察者那 里取出事件并处理
Windows下,这个循环基于IOCP创建,而在*nix下则基于多线程创建

整个异步I/O的流程图如下
在这里插入图片描述事件循环、观察者、请求对象、I/O线程池这四者共同构成了Node异步I/O模型的基本要素。

事实上,在Node中, 除了JavaScript是单线程外,Node自身其实是多线程的

非 I/O 的异步 API

尽管我们在介绍Node的时候,多数情况下都会提到异步I/O,但是Node中其实还存在一些与 I/O无关的异步API,这一部分也值得略微关注一下,它们分别是setTimeout()、setInterval()、 setImmediate()和process.nextTick()
setTimeout()和setInterval()与浏览器中的API是一致的,分别用于单次和多次定时执行任 务。它们的实现原理与异步I/O比较类似,只是不需要I/O线程池的参与。调用setTimeout()或者 setInterval()创建的定时器会被插入到定时器观察者内部的一个红黑树中。。每次Tick执行时,会从该红黑树中迭代取出定时器对象,检查是否超过定时时间,如果超过,就形成一个事件,它的 回调函数将立即执行

在这里插入图片描述
process.nextTick()
每次调用process.nextTick()方法,只会将回调函数放入队列中,在下一轮Tick时取出执行。 定时器中采用红黑树的操作时间复杂度为O(lg(n)),nextTick()的时间复杂度为O(1)。相较之下, process.nextTick()更高效。
setImmediate()
setImmediate()方法与process.nextTick()方法十分类似,都是将回调函数延迟执行。
但是两者之间其实是有细微差别的。将它们放在一起时,又会是怎样的优先级呢。示例代码 如下:

setImmediate(function () {
    console.log('setImmediate延迟执行'); 
});

process.nextTick(function () { 
    console.log('nextTick延迟执行');
});

console.log('正常执行');
//正常执行
//nextTick延迟执行
//setImmediate延迟执行

从结果里可以看到,process.nextTick()中的回调函数执行的优先级要高于setImmediate()。 这里的原因在于事件循环对观察者的检查是有先后顺序的,process.nextTick()属于idle观察者, setImmediate()属于check观察者。在每一个轮循环检查中:

idle观察者 先于 I/O观察者 先于 check观察者

在具体实现上,process.nextTick()的回调函数保存在一个数组中,setImmediate()的结果 则是保存在链表中

  • process.nextTick : 数组中
  • setImmediate: 链表中

主模块内调用(通常情况下):
process.nextTick 先于 setTimeout 先于 setImmediate 先于 I/O
I/O操作中
process.nextTick 先于setImmediate 先于 setTimeout 先于 I/O

发布了77 篇原创文章 · 获赞 7 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_37653449/article/details/89437942