Node异步I/O相关知识点(二)

2022-07-14 21:09:47 浏览数 (1)

当今社会,技术的作用已经越来越明显,未来社会中,谁掌握了先进的技术,谁就可以呼风唤雨。

前情回顾

上篇文章主要分享了异步I/O的阻塞,非阻塞问题,因为它们会对系统的性能有所影响。今天主要聊一下Node异步I/O中的事件循环和JS中的事件循环

JS的并发模型和事件循环

JavaScript有个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务,这个模型和其他语言截然不同。

想要理解这个模型,需要先理解这几个概念堆(heap),栈(stack),队列。在JS中,堆内存的作用在于提供引用类型的存储空间。栈内存的作用有两个:1,存放基本数据类型。2,提供代码的运行环境。提供运行环境其实是函数的调用形成了一个多帧组成的栈。如图(这个图有点秀,不要在意这些细节):

事件循环模型

看下面的代码:

代码语言:javascript复制
function foo(b){
  let a = 10;
  return a b;
}
function bar(x){
  return foo(x*y)
}
console.log(bar(7))

当调用bar()的时候,第一帧被压入栈中,包含了bar的参数和局部变量。当bar调用foo时,第二帧创建并压入栈中,放在第一帧上面,帧中包含foo的参数和局部变量。当foo执行完成后,第二帧就被弹出。当bar执行完成后,第一帧也被弹出,栈就清空了。

需要注意的是,一个JavaScript运行时包含了一个带处理消息的消息队列。每个消息都关联一个用于处理这个消息的回调函数。这个可以理解为上图底部的message

在js事件循环的某个时刻,运行时会从最先进入队列的消息开始处理队列中的消息。被处理的消息会被移出队列,并作为输入参数来调用与之关联的函数。

函数的处理会一直进行到执行栈再次为空为止,然后事件循环队列会处理队列中的下一个消息。

事件循环的实现代码大致如下:

代码语言:javascript复制
while(queue.waitForMessage()){
  queue.processNextMessage()
}

queue.processNextMessage()会同步等待消息的到达。每个消息完整的执行完成后,其他消息才会被执行。

那么,消息是什么?这里可以理解为事件的回调函数。在浏览器中,每个事件发生并且有一个事件监听器绑定在该事件上时,一个消息就会被添加到消息队列。如果没有事件监听器,这个事件就会丢失。所以当一个带有点击事件处理器的元素被点击时,就会像其他事件一样产生一个类似的消息。

再说一下setTimeout,setTimeout函数接受两个参数:待加入队列的消息(即回调函数)和一个时间值。这个时间值代表的是这个消息(回调函数)被实际加到消息队列的最小延迟时间。如果队列中没有其他消息且执行栈为空,在这段时间过去后,消息会马上处理。但是如果有其他消息,setTimeout必须等到其他消息处理完成。所以,setTimeout的第二个参数仅仅代码最少延迟时间,并非确切的时间。

代码语言:javascript复制
const s = new Date().getSeconds();
setTimeout(function(){
  console.log('run after '  (new Date().getSeconds()-s)   'seconds');

},500)

while(true){
  if(new Date().getSeconds()-s>=2){
    console.log('looped for 2 seconds')
    break;
  }
}
// looped for 2 seconds
// run after 2 seconds

可以看到执行代码后先输出了 looped for 2 seconds,然后输出run after 2 seconds。因为代码执行到setTimeout,发现他是一个消息,将它加入到了队列中,队列清空后,才又开始处理这个消息。

Node异步IO中的事件循环

Node自身的执行模型也叫事件循环。在进程启动时,Node会创建一个类似while(true)的循环,每执行一次循环体的过程被称为Tick。Tick的过程就是查看是否有事件待处理,如果有,就取出事件及其相关的回调函数。有关联的回调函数就执行它们。然后开始下个循环,如果没有事件,就退出进程。

那么在每次Tick的过程中,如何判断是否还有事件需要处理呢?这里就引入了观察者,个人的理解就是观察者模式,并且有可能Vue的实现也是借鉴了Node的这个理念。

非I/O的异步API

在面试的时候有时会问到异步的问题,最多的是promise相关的问题。涉及到的有setTimeout,process.nextTick,setImmediate相关的宏任务微任务的问题。这几个实际上是Node里的几个非I/O的异步API

setTimeout()setInterval()与浏览器中的API是一致的,分别用于单次和多次定时执行任务。它们的实现原理与异步I/O比较类似,只是不需要I/O线程池的参与。调用setTimeout()或者setInterval()创建的定时器会被插入到定时器观察者内部的一个红黑树中。每次Tick执行时,会从该红黑树中迭代取出定时器对象,检查是否超过定时时间,如果超过,就形成一个事件,它的回调函数将立即执行。

总结

  • js中的事件循环机制
  • Node中的事件循环

javascript基础知识总结

0 人点赞