关于Nodejs的运行机制

上一次我们讲了一下JavaScript的运行机制,这次我们来谈谈nodejs的运行机制,相信有了js的基础,关于nodejs 的运行机制我们可以很清楚的理解了,如果有不了解js运行机制的,可以看我的上一篇文章

https://mp.csdn.net/postedit/90691003

一、Nodejs的架构

首先上张图

关于Nodejs的运行机制

这张图是从github下扒下来的,首先我们可以看到,nodejs的架构是分为三层的。

第一层就是我们的用户代码,用户代码即我们编写的应用程序代码、npm包、nodejs内置的js模块等,我们日常工作中的大部分时间都是编写这个层面的代码。

第二层是胶水代码或第三方插件层,能够让js调用c/c++的代码。我们可以将其理解为一个桥,桥这头是js,另一边是c或者c++,通过这个桥可以让js调用c或者c++。第三方插件是我们自己实现的c或c++库,同时需要我们自己实现胶水代码,将js和c/c++链接

第三层就是底层库。nodejs的 依赖库,有v8、linuv......

v8是google开发的一套高效的js引擎,nodejs能够高效执行js的代码的很大的原因就在它。

libuv:是用c语言实现的一套异步功能库,nodejs高效的异步编程模型很大程度上归功于libuv的实现

还有一些其他的依赖库:http-parser,npm......

libuv架构

由上面的内容,我们已经知道,nodejs实现异步编程的核心就是libuv,libuv承担着nodejs与文件,网络等异步任务的沟通桥梁。

首先我们来谈谈nodejs中的异步事件

  • 非I/O:
    • 定时器(setTimeout,setInterval)
    • mircroTask(promise.then)
    • process.nextTick()
    • setImmediate
    • DNS.lookup
  • I/O
    • 网络
    • 文件
    • DNS操作

文件I/O、异步DNS操作

libuv内部还维护着一个默认4个线程的线程池,这些线程负责执行文件I/O操作,DNS操作,用于异步代码。每当js层传递

给libuv一个操作任务时,libuv就会把这个任务添加到队列中,之后分为两种情况

  1. 线程池内的线程都被占用的时候,队列中任务就要进行排队等待空闲线程
  2. 线程池中有可用线程时,从队列中取出这个任务执行,执行完毕后,线程归还给线程池,等待下一个任务到来,同时以时间的方式通知event-loop,event-loop接收到事件后,执行该事件的回调函数

notes:对于4个线程的线程池,我们可以在nodejs启动的时候,设置环境变量UV_THREADPOOL_SIZE来调整,但是最大不能超过128个

MacroTask和MicroTask

首先在介绍微任务队列和宏任务之前,我们首先介绍一下nodejs中独有的processs.nextTick(),它并不属于liuv。nodejs中要处理的回调,除了libuv中要处理的之外,在nodejs中还有另外两个queue,第一个是nextTick Queue,专门用于存储process.nextTick(),第二个是MicroTask Queue,包含一些microTask任务。

接下来到我们的重点:nodejs的时间循环

关于Nodejs的运行机制

在EventLoop完成一个阶段,到下一个阶段之前,Event Loop将会执行这nextTick Queue以及microTask queue里面的回调,直到这两个队列为空,一旦这两个队列为空之后,就会进入下一个阶段。同时在nextTick Queue和microTask Queue之间是有一个执行的先后顺序的,nextTick queue会先于MicroTask queue执行的

事件循环的原理

   ┌───────────────────────┐
┌─>│        timers                                     │
│  └──────────┬────────── ─┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks                                  │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare                                  │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming.                    │
│  │         poll                                             │<─┤  connections,       │
│  └──────────┬────────────┘      │   data, etc.                     │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check                                          │
│  └──────────┬────────────┘
│  ┌──────────┴─────────── ┐
└ ┤    close callbacks                              │
   └───────────────────────┘

  • node的初始化
    • 初始化node环境
    • 执行输入代码
    • 执行process.nextTick回调
    • 执行微任务队列
  • 进入event-loop
    • 进入timers阶段(执行setTimeout和SetInterval)
      • 检查timer队列是否有到期的timer回调,如果有,将到期的timer回调按照timerid升序进行
      • 检查是否有process.nextTick任务,如果有全部执行
      • 检查是否有microtask,有全部执行
      • 退出该阶段
    • 进入IO阶段
      • 检查是否有pending的io回调,如果有,执行回调,如果没有,退出该阶段
      • 检查是否有process.nextTick()任务,如果有,全部执行
      • 检查是否有MicroTask,如果有全部执行
      • 退出该阶段
    • 进入idle,prepare阶段
    • 进入poll阶段
      • 首先检查是否存在尚未完成的回调,存在,则
        • 1.如果 有可用回调,
          • 那么执行
          • 检查是否有process.nextTick()回调,有全部执行
          • 检查是否有MicroTask,如果有全部执行
          • 退出该阶段
        • 2.如果没有可用回调
          • 检查是否有immediate回调,如果有退出poll阶段,如果没有,阻塞在此阶段,等待新的事件通知
      • 如果不存在尚未完成的回调,退出poll阶段
    • 进入check阶段
      • 如果有immediate回调,执行所有回调
      • 检查是否有process.nextTick()回调,如果有,全部执行
      • 检查是否有MicroTask,如果有全部执行
      • 退出该阶段
    • 进入closing阶段
      • 如果有immediate回调,则执行所有immediate回调。

      • 检查是否有 process.nextTick 回调,如果有,全部执行。

      • 检查是否有 microtaks,如果有,全部执行。

      • 退出 closing 阶段

    • 检查是否有活跃的handles(定时器、io等事件)

      • 如果有,继续下一轮循环

      • 没有则结束事件循环,退出程序

以上就是nodejs中代码的执行过程,接下来我们上一段代码来检测我们学习的怎么样吧

Promise.resolve().then(() => {
  console.log('resolve1');
});

process.nextTick(function() {
  console.log('tick1');
  process.nextTick(function() {
    console.log('tick2');
  });
  process.nextTick(function() {
    console.log('tick3');
  });
});

Promise.resolve().then(() => {
  console.log('resolve2');
});

process.nextTick(function() {
  console.log('tick4');
});


Promise.resolve().then(() => {
  console.log('resolve3');
});

process.nextTick(function() {
  console.log('tick5');
});

答案在下方,各位小伙伴可以先思考一下

 

 

tick1

tick4

tick5

tick2

tick3

resolve1

resolve2

resolve3

上一篇:Vue开发--vue中在页面渲染完之后获取元素(否则动态渲染的元素获取不到)


下一篇:Event Loop 在浏览器和Node端的差异