我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Node.js事件循环是什么

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Node.js事件循环是什么

本文小编为大家详细介绍“Node.js事件循环是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“Node.js事件循环是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

什么是事件循环

首先我们需要了解一下最基础的一些东西,比如这个事件循环,事件循环是指Node.js执行非阻塞I/O操作,尽管==JavaScript是单线程的==,但由于大多数==内核都是多线程==的,Node.js会尽可能将操作装载到系统内核。因此它们可以处理在后台执行的多个操作。当其中一个操作完成时,内核会告诉Node.js,以便Node.js可以将相应的回调添加到轮询队列中以最终执行。

当Node.js启动时会初始化event loop, 每一个event loop都会包含按如下顺序六个循环阶段:

   ┌───────────────────────┐┌─>│        timers         ││  └──────────┬────────────┘│  ┌──────────┴────────────┐│  │     I/O callbacks     ││  └──────────┬────────────┘│  ┌──────────┴────────────┐│  │     idle, prepare     ││  └──────────┬────────────┘      ┌───────────────┐│  ┌──────────┴────────────┐      │   incoming:   ││  │         poll          │<─────┤  connections, ││  └──────────┬────────────┘      │   data, etc.  ││  ┌──────────┴────────────┐      └───────────────┘│  │        check          ││  └──────────┬────────────┘│  ┌──────────┴────────────┐└──┤    close callbacks    │   └───────────────────────┘
  • 1. timers 阶段: 这个阶段执行 setTimeout(callback) 和 setInterval(callback) 预定的 callback;

  • 2. I/O callbacks 阶段: 此阶段执行某些系统操作的回调,例如TCP错误的类型。 例如,如果TCP套接字在尝试连接时收到 ECONNREFUSED,则某些* nix系统希望等待报告错误。 这将操作将等待在==I/O回调阶段==执行;

  • 3. idle, prepare 阶段: 仅node内部使用;

  • 4. poll 阶段: 获取新的I/O事件, 例如操作读取文件等等,适当的条件下node将阻塞在这里;

  • 5. check 阶段: 执行 setImmediate() 设定的callbacks;

  • 6. close callbacks 阶段: 比如 socket.on(‘close’, callback) 的callback会在这个阶段执行;

事件循环详解

这个图是整个 Node.js 的运行原理,从左到右,从上到下,Node.js 被分为了四层,分别是 应用层V8引擎层Node API层 和 LIBUV层

  • 应用层: 即 JavaScript 交互层,常见的就是 Node.js 的模块,比如 http,fs

  • V8引擎层: 即利用 V8 引擎来解析JavaScript 语法,进而和下层 API 交互

  • NodeAPI层: 为上层模块提供系统调用,一般是由 C 语言来实现,和操作系统进行交互 。

  • LIBUV层: 是跨平台的底层封装,实现了 事件循环、文件操作等,是 Node.js 实现异步的核心 。

每个循环阶段内容详解

timers阶段 一个timer指定一个下限时间而不是准确时间,在达到这个下限时间后执行回调。在指定时间过后,timers会尽可能早地执行回调,但系统调度或者其它回调的执行可能会延迟它们。

  • 注意:技术上来说,poll 阶段控制 timers 什么时候执行。

  • 注意:这个下限时间有个范围:[1, 2147483647],如果设定的时间不在这个范围,将被设置为1。

I/O callbacks阶段 这个阶段执行一些系统操作的回调。比如TCP错误,如一个TCP socket在想要连接时收到ECONNREFUSED, 类unix系统会等待以报告错误,这就会放到 I/O callbacks 阶段的队列执行. 名字会让人误解为执行I/O回调处理程序, 实际上I/O回调会由poll阶段处理.

poll阶段 poll 阶段有两个主要功能:(1)执行下限时间已经达到的timers的回调,(2)然后处理 poll 队列里的事件。 当event loop进入 poll 阶段,并且 没有设定的 timers(there are no timers scheduled),会发生下面两件事之一:

  • 如果 poll 队列不空,event loop会遍历队列并同步执行回调,直到队列清空或执行的回调数到达系统上限;

  • 如果 poll 队列为空,则发生以下两件事之一:

    • 如果代码已经被setImmediate()设定了回调, event loop将结束 poll 阶段进入 check 阶段来执行 check 队列(里面的回调 callback)。

    • 如果代码没有被setImmediate()设定回调,event loop将阻塞在该阶段等待回调被加入 poll 队列,并立即执行。

  • 但是,当event loop进入 poll 阶段,并且 有设定的timers,一旦 poll 队列为空(poll 阶段空闲状态): event loop将检查timers,如果有1个或多个timers的下限时间已经到达,event loop将绕回 timers 阶段,并执行 timer 队列。

check阶段 这个阶段允许在 poll 阶段结束后立即执行回调。如果 poll 阶段空闲,并且有被setImmediate()设定的回调,event loop会转到 check 阶段而不是继续等待。

  • setImmediate() 实际上是一个特殊的timer,跑在event loop中一个独立的阶段。它使用libuv的API 来设定在 poll 阶段结束后立即执行回调。

  • 通常上来讲,随着代码执行,event loop终将进入 poll 阶段,在这个阶段等待 incoming connection, request 等等。但是,只要有被setImmediate()设定了回调,一旦 poll 阶段空闲,那么程序将结束 poll 阶段并进入 check 阶段,而不是继续等待 poll 事件们 (poll events)。

close callbacks 阶段 如果一个 socket 或 handle 被突然关掉(比如 socket.destroy()),close事件将在这个阶段被触发,否则将通过process.nextTick()触发

这里呢,我们通过伪代码来说明一下,这个流程:

// 事件循环本身相当于一个死循环,当代码开始执行的时候,事件循环就已经启动了// 然后顺序调用不同阶段的方法while(true){// timer阶段    timer()// I/O callbacks阶段    IO()// idle阶段    IDLE()// poll阶段    poll()// check阶段    check()// close阶段    close()}// 在一次循环中,当事件循环进入到某一阶段,加入进入到check阶段,突然timer阶段的事件就绪,也会等到当前这次循环结束,再去执行对应的timer阶段的回调函数 // 下面看这里例子const fs = require('fs')// timers阶段const startTime = Date.now();setTimeout(() => {    const endTime = Date.now()    console.log(`timers: ${endTime - startTime}`)}, 1000)// poll阶段(等待新的事件出现)const readFileStart =  Date.now();fs.readFile('./Demo.txt', (err, data) => {    if (err) throw err    let endTime = Date.now()    // 获取文件读取的时间    console.log(`read time: ${endTime - readFileStart}`)    // 通过while循环将fs回调强制阻塞5000s    while(endTime - readFileStart < 5000){        endTime = Date.now()    }})// check阶段setImmediate(() => {    console.log('check阶段')})

走进案例解析

我们来看一个简单的EventLoop的例子:

const fs = require('fs');let counts = 0;// 定义一个 wait 方法function wait (mstime) {  let date = Date.now();  while (Date.now() - date < mstime) {    // do nothing  }}// 读取本地文件 操作IOfunction asyncOperation (callback) {  fs.readFile(__dirname + '/' + __filename, callback);}const lastTime = Date.now();// setTimeoutsetTimeout(() => {  console.log('timers', Date.now() - lastTime + 'ms');}, 0);// process.nextTickprocess.nextTick(() => {  // 进入event loop  // timers阶段之前执行  wait(20);  asyncOperation(() => {    console.log('poll');  });  });

这里呢,为了让这个setTimeout优先于fs.readFile 回调, 执行了process.nextTick, 表示在进入timers阶段前, 等待20ms后执行文件读取.

1. nextTick 与 setImmediate

  • process.nextTick 不属于事件循环的任何一个阶段,它属于该阶段与下阶段之间的过渡, 即本阶段执行结束, 进入下一个阶段前, 所要执行的回调。有给人一种插队的感觉.

  • setImmediate 的回调处于check阶段, 当poll阶段的队列为空, 且check阶段的事件队列存在的时候,切换到check阶段执行,参考nodejs进阶视频讲解:进入学习

nextTick 递归的危害

由于nextTick具有插队的机制,nextTick的递归会让事件循环机制无法进入下一个阶段. 导致I/O处理完成或者定时任务超时后仍然无法执行, 导致了其它事件处理程序处于饥饿状态. 为了防止递归产生的问题, Node.js 提供了一个 process.maxTickDepth (默认 1000)。

const fs = require('fs');let counts = 0;function wait (mstime) {  let date = Date.now();  while (Date.now() - date < mstime) {    // do nothing  }}function nextTick () {  process.nextTick(() => {    wait(20);    console.log('nextTick');    nextTick();  });}const lastTime = Date.now();setTimeout(() => {  console.log('timers', Date.now() - lastTime + 'ms');}, 0);nextTick();

此时永远无法跳到timer阶段去执行setTimeout里面的回调方法, 因为在进入timers阶段前有不断的nextTick插入执行. 除非执行了1000次到了执行上限,所以上面这个案例会不断地打印出nextTick字符串

2. setImmediate

如果在一个I/O周期内进行调度,setImmediate() 将始终在任何定时器(setTimeout、setInterval)之前执行.

3. setTimeout 与 setImmediate
  • setImmediate()被设计在 poll 阶段结束后立即执行回调;

  • setTimeout()被设计在指定下限时间到达后执行回调;

无 I/O 处理情况下:

setTimeout(function timeout () {  console.log('timeout');},0);setImmediate(function immediate () {  console.log('immediate');});

执行结果:

C:\Users\92809\Desktop\node_test>node test.jstimeoutimmediateC:\Users\92809\Desktop\node_test>node test.jstimeoutimmediateC:\Users\92809\Desktop\node_test>node test.jstimeoutimmediateC:\Users\92809\Desktop\node_test>node test.jsimmediatetimeout

从结果,我们可以发现,这里打印输出出来的结果,并没有什么固定的先后顺序,偏向于随机,为什么会发生这样的情况呢?

答:首先进入的是timers阶段,如果我们的机器性能一般,那么进入timers阶段,1ms已经过去了 ==(setTimeout(fn, 0)等价于setTimeout(fn, 1))==,那么setTimeout的回调会首先执行。

如果没有到1ms,那么在timers阶段的时候,下限时间没到,setTimeout回调不执行,事件循环来到了poll阶段,这个时候队列为空,于是往下继续,先执行了setImmediate()的回调函数,之后在下一个事件循环再执行setTimemout的回调函数。

问题总结:而我们在==执行启动代码==的时候,进入timers的时间延迟其实是==随机的==,并不是确定的,所以会出现两个函数执行顺序随机的情况。

那我们再来看一段代码:

var fs = require('fs')fs.readFile(__filename, () => {    setTimeout(() => {        console.log('timeout');    }, 0);    setImmediate(() => {        console.log('immediate');    });});

打印结果如下:

C:\Users\92809\Desktop\node_test>node test.jsimmediatetimeoutC:\Users\92809\Desktop\node_test>node test.jsimmediatetimeoutC:\Users\92809\Desktop\node_test>node test.jsimmediatetimeout# ... 省略 n 多次使用 node test.js 命令 ,结果都输出 immediate timeout

这里,为啥和上面的随机timer不一致呢,我们来分析下原因:

原因如下:fs.readFile的回调是在poll阶段执行的,当其回调执行完毕之后,poll队列为空,而setTimeout入了timers的队列,此时有代码 setImmediate(),于是事件循环先进入check阶段执行回调,之后在下一个事件循环再在timers阶段中执行回调。

当然,下面的小案例同理:

setTimeout(() => {    setImmediate(() => {        console.log('setImmediate');    });    setTimeout(() => {        console.log('setTimeout');    }, 0);}, 0);

以上的代码在timers阶段执行外部的setTimeout回调后,内层的setTimeoutsetImmediate入队,之后事件循环继续往后面的阶段走,走到poll阶段的时候发现队列为空,此时有代码有setImmedate(),所以直接进入check阶段执行响应回调(==注意这里没有去检测timers队列中是否有成员到达下限事件,因为setImmediate()优先==)。之后在第二个事件循环的timers阶段中再去执行相应的回调。

综上所演示,我们可以总结如下:

  • 如果两者都在主模块中调用,那么执行先后取决于进程性能,也就是你的电脑好撇,当然也就是随机。

  • 如果两者都不在主模块调用(被一个异步操作包裹),那么**setImmediate的回调永远先执行**。

4. nextTick 与 Promise

概念:对于这两个,我们可以把它们理解成一个微任务。也就是说,它其实不属于事件循环的一部分。 那么他们是在什么时候执行呢? 不管在什么地方调用,他们都会在其所处的事件循环最后,事件循环进入下一个循环的阶段前执行。

setTimeout(() => {    console.log('timeout0');    new Promise((resolve, reject) => { resolve('resolved') }).then(res => console.log(res));    new Promise((resolve, reject) => {      setTimeout(()=>{        resolve('timeout resolved')      })    }).then(res => console.log(res));    process.nextTick(() => {        console.log('nextTick1');        process.nextTick(() => {            console.log('nextTick2');        });    });    process.nextTick(() => {        console.log('nextTick3');    });    console.log('sync');    setTimeout(() => {        console.log('timeout2');    }, 0);}, 0);

控制台打印如下:

C:\Users\92809\Desktop\node_test>node test.jstimeout0syncnextTick1nextTick3nextTick2resolvedtimeout2timeout resolved

最总结:timers阶段执行外层setTimeout的回调,遇到同步代码先执行,也就有timeout0sync的输出。遇到process.nextTickPromise后入微任务队列,依次nextTick1nextTick3nextTick2resolved入队后出队输出。之后,在下一个事件循环的timers阶段,执行setTimeout回调输出timeout2以及微任务Promise里面的setTimeout,输出timeout resolved。(这里要说明的是 微任务nextTick优先级要比Promise要高)

5. 最后案例

代码片段1:

setImmediate(function(){  console.log("setImmediate");  setImmediate(function(){    console.log("嵌套setImmediate");  });  process.nextTick(function(){    console.log("nextTick");  })});

解析:

事件循环check阶段执行回调函数输出setImmediate,之后输出nextTick。嵌套的setImmediate在下一个事件循环的check阶段执行回调输出嵌套的setImmediate

代码片段2:

async function async1(){    console.log('async1 start')    await async2()    console.log('async1 end')  }async function async2(){    console.log('async2')}console.log('script start')setTimeout(function(){    console.log('setTimeout0') },0)  setTimeout(function(){    console.log('setTimeout3') },3)  setImmediate(() => console.log('setImmediate'));process.nextTick(() => console.log('nextTick'));async1();new Promise(function(resolve){    console.log('promise1')    resolve();    console.log('promise2')}).then(function(){    console.log('promise3')})console.log('script end')

打印结果为:

C:\Users\92809\Desktop\node_test>node test.jsscript startasync1 startasync2promise1promise2script endnextTickpromise3async1 endsetTimeout0setTimeout3setImmediate

读到这里,这篇“Node.js事件循环是什么”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网行业资讯频道。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Node.js事件循环是什么

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Node.js事件循环是什么

本文小编为大家详细介绍“Node.js事件循环是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“Node.js事件循环是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。什么是事件循环首先我们需要了解一下最基
2023-07-04

Node.js中事件循环的机制是什么

本篇内容介绍了“Node.js中事件循环的机制是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!先看一个demo:setTimeout((
2023-06-17

nodejs事件循环是什么

这篇文章主要讲解了“nodejs事件循环是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“nodejs事件循环是什么”吧!我们都听说过 JavaScript 和 Node.js 是单线程的
2023-07-04

Node.js事件循环怎么实现

这篇文章主要介绍“Node.js事件循环怎么实现”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Node.js事件循环怎么实现”文章能帮助大家解决问题。在进程启动时,Node 便会创建一个类似于 wh
2023-07-04

Node的事件循环是什么

这篇文章主要介绍“Node的事件循环是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Node的事件循环是什么”文章能帮助大家解决问题。一、什么是事件循环一句话:事件循环是Nodejs处理异步操作
2023-07-05

Node事件循环机制是什么

这篇文章主要介绍“Node事件循环机制是什么”,在日常操作中,相信很多人在Node事件循环机制是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Node事件循环机制是什么”的疑惑有所帮助!接下来,请跟着小编
2023-07-05

深入浅析Node.js 事件循环

Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高。 (来源于Javascript是单线程又是异步的,但是这种语言有个共同的特点:它们是 event-driven 的。驱动它们的 event 来自一个异构的平
2022-06-04

Node.js中事件循环的方法

本文小编为大家详细介绍“Node.js中事件循环的方法”,内容详细,步骤清晰,细节处理妥当,希望这篇“Node.js中事件循环的方法”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。Node 自身的执行模型——事件循
2023-06-17

JavaScript事件循环的原理是什么

今天小编给大家分享一下JavaScript事件循环的原理是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。理解 JavaS
2023-07-04

Node.js 事件循环详解及实例

Node.js 事件循环详解及实例Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高。Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发。Node.js 基本
2022-06-04

Node.js 事件循环:从概念到掌握

Node.js 事件循环是一种异步编程模型,使开发者能够构建高性能且可扩展的应用程序。本文深入探讨了 Node.js 事件循环的概念,从基础开始,逐步深入到掌握它的方方面面。
Node.js 事件循环:从概念到掌握
2024-03-06

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录