首页前端开发JavaScript一文聊聊Node.js中的event-loop机制

一文聊聊Node.js中的event-loop机制

时间2024-01-30 17:05:03发布访客分类JavaScript浏览775
导读:收集整理的这篇文章主要介绍了一文聊聊Node.js中的event-loop机制,觉得挺不错的,现在分享给大家,也给大家做个参考。本篇文章带大家一起来了解一下Node.js中的event-loop(时间循环)机制,希望对大家有所帮助!今天我们...
收集整理的这篇文章主要介绍了一文聊聊Node.js中的event-loop机制,觉得挺不错的,现在分享给大家,也给大家做个参考。本篇文章带大家一起来了解一下Node.js中的event-loop(时间循环)机制,希望对大家有所帮助!

今天我们来学习下nodeJs中的event-loop。event-loop的理解对我来讲一直都是一个比较大的难点,希望通过这次的学习把这个难点突破,也希望能通过这篇博客加深自己对event-loop的理解和印象。

libuv

在学习event-loop之前,先了解下node的libuv。libuv负责不同操作系统上的不同I/O模型的实现,并且把不同的实现抽象为能应用与第三方应用程序的API。

问题

在正式学习event-loop前,先思考一个问题

    setTimeout(() =>
 {
          console.LOG("timer1");
          Promise.resolve().then(() =>
 {
            console.log("PRomise1");
      }
    );
    }
    , 0);
        setTimeout(() =>
 {
          console.log("timer2");
          Promise.resolve().then(() =>
 {
            console.log("promise2");
      }
    );
    }
    , 0);
    

这段代码在浏览器中运行的结果是怎样的?

在node中运行的结果又是怎样的呢?

在node8.6之前:

node8.6之后:

为什么会有这样的结果,我们稍后分析!

nodeJs 中的event-loop

首先,来看一张图:

在图中可以看到6个阶段,分别是:timers,PEnding callbacks,idle/prepare,poll,check,close callbacks。

  • timers阶段:主要执行setTimeOut,setInterval的回调

  • pending callbacks阶段:执行一些系统调用的错误,比如说网络通信的错误回调

  • idle/prepare阶段:只在系统内部使用(这个阶段我们控制干涉不了)

  • poll阶段:获取新的I/O事件,比如获取一个读取文件的I/O回调。在适合的情况下,nodejs将阻塞在这个阶段

  • check阶段:执行 setImmediate的回调

  • 比如执行sokect的destory,close事件回调

每一个阶段都遵循一个FIFO(先入先出)的规则来执行任务队列里面的任务。在这六个阶段中,我们着重需要关注的是timers,poll,check阶段。我们日常开发中的绝大部分异步任务都是在这三个阶段处理的。

timers

我们先来说一说timers阶段。
timers是事件循环的第一个阶段,nodejs会去检查有没有已经过期了的timer,如果有,就将它的回调放入队列中。但是nodejs并不能保证timer在预设事件到了就会立即执行回调,这是因为nodejs对timer的过期检查不一定靠谱,它会受机器上其他运行程序的影响,或者是会遇到当前主线程不空闲的情况。
对于这里的不确定性,官网上举了一个例子:
先声明一个setTimeOut,然后外部读取一个文件,当读取文件操作超过定时器的时间,这样一来读文件操作就会把定时器的回调延后,这就是前面说的主线程不空闲的情况。

poll

poll阶段主要是执行两件事情:

1、处理poll阶段的任务队列

2、当有了已经超时的timer执行它的回调函数

在上图中,我们还可以看到:在Poll阶段执行完poll任务队列的任务之后,会去检查有无预设的setImmediate,如果有,则进入check阶段,如果没有,则nodejs将会阻塞在这里。

这里我们就会有一个疑问了,如果阻塞在poll阶段,那我们设置的timer岂不是执行不了了吗?
其实当event-loop阻塞在poll阶段时,nodejs会有一个检查机制,它会去检查timers队列是否为空,如果不为空,则重新进入timers阶段。

check

check阶段主要时执行setImmediate的回调函数。

小总结

event-loop的每个阶段都有一个队列,当event-loop达到某个阶段之后,将执行这个阶段的任务队列,直到队列清空或者达到系统规定的最大回调限制之后,才会进入下一个阶段。当所有阶段都执行完成一次之后,称event-loop完成一个tick。

案例

上面我们说完了event-loop的理论部分,但是光有理论我们也还是不能很清晰的理解event-loop。下面我们就根据几个demo来更加深入的理解下event-loop!

demo1

    const fs=require('fs')    fs.reaDFile('test.txt',()=>
{
                console.log('readFile')            setTimeout(()=>
{
                        console.log('settimeout');
            }
    ,0)            setImmediate(()=>
{
                    console.log('setImmediate')            }
)    }
    )

执行结果:

可见执行结果跟我们前面的分析时一致的!

demo2

    const fs = require("fs");
        const EventEmITter = require("events").EventEmitter;
        let pos = 0;
        const messenger = new EventEmitter();
    messenger.on("message", function (msg) {
          console.log(++pos + " message:" + msg);
 //    }
    );
        console.log(++pos + " First");
 //    process.nextTick(function () {
          console.log(++pos + " nextTick");
 //    }
    );
        messenger.emit("message", "hello!");
    fs.stat(__filename, function () {
          console.log(++pos + " stat");
 //    }
    );
    setTimeout(function () {
          console.log(++pos + " quick timer");
 //    }
    , 0);
    setTimeout(function () {
          console.log(++pos + " long timer");
 //    }
    , 30);
    setImmediate(function () {
          console.log(++pos + " immediate");
 //    }
    );
        console.log(++pos + " last");
     //

结果:

了解下浏览器和node的event-loop差异在什么地方

在node 8.6 之前:

浏览器中的微任务队列会在每个宏任务执行完成之后执行,而node中的微任务会在事件循环的各个阶段之间执行,即每个阶段执行完成之后会去执行微任务队列。

在8.6之后:

浏览器和node中微任务的执行是一致的!

所以,在文章开头,我们提出的思考的问题就有了结果。

关于 process.nextTick()和setImmediate

process.nextTick()

语法:process.nextTick(callback,agrs)

执行时机:

这个函数其实是独立于 Event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行。递归的调用process.nextTick()会导致I/O starving,官方推荐使用setImmediate()

关于starving现象的说明:

    const fs = require("fs");
        fs.readFile("test.txt", (err, msg) =>
 {
          console.log("readFile");
    }
    );
        let index = 0;
    function handler() {
          if (index >
    = 30) return;
          index++;
          console.log("nextTick" + index);
          process.nextTick(handler);
    }
        handler();
    

运行结果:

可以看到,等到nextTick函数呗执行30次之后,读取文件的回调才被执行!这样的现象被称为 I/O 饥饿

当我们把 process.nextTick 换为 setImmediate

    const fs = require("fs");
        fs.readFile("test.txt", (err, msg) =>
 {
          console.log("readFile");
    }
    );
        let index = 0;
    function handler() {
          if (index >
    = 30) return;
          index++;
          console.log("nextTick" + index);
          setImmediate(handler);
    }
        handler();
    

结果:

造成这两种差异的原因是,嵌套调用的setImmediate的回调被排到了下一次event-loop中去!

event-loop核心思维导图

结束语

通过今天的学习,让我对event-loop的理解更深刻了。那么,下次见。好好学习,天天向上!

更多编程相关知识,请访问:编程视频!!

以上就是一文聊聊Node.js中的event-loop机制的详细内容,更多请关注其它相关文章!

声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!

上一篇: 浅谈Angular中的预加载配置懒加...下一篇:javascript是什么线程猜你在找的JavaScript相关文章 html font标签如何设置字体大小?html font标签属性用法介绍2022-05-16vue3+TypeScript+vue-router的使用方法2022-04-16vue3获取当前路由地址2022-04-16如何利用React实现图片识别App2022-04-16JavaScript展开运算符和剩余运算符的区别详解2022-04-16微信小程序中使用vant框架的具体步骤2022-04-16Vue elementUI表单嵌套表格并对每行进行校验详解2022-04-16如何利用Typescript封装本地存储2022-04-16微信小程序中wxs文件的一些妙用分享2022-04-16JavaScript的Set数据结构详解2022-04-16 其他相关热搜词更多phpjavapython程序员load

若转载请注明出处: 一文聊聊Node.js中的event-loop机制
本文地址: https://pptw.com/jishu/592797.html
asp.net mvc如何动态编译生成Controller的方法示例详解 ASP.NET Core Razor页面路由的详细介绍

游客 回复需填写必要信息