nodejs里面的event loop

news2024/11/19 3:27:50

1. event loop

1.1 什么是event-loop

js的标准文档定义如下
https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#event_loop
https://javascript.info/event-loop

html的标准定义
https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model

nodejs自己实现了一套event-loop,与html/js的都不一样

  • https://github.com/nodejs/nodejs.dev/blob/main/src/documentation/0029-node-event-loop/index.md
  • https://nodejs.org/uk/docs/guides/event-loop-timers-and-nexttick/
  • https://stackoverflow.com/questions/31582672/what-is-the-different-between-javascript-event-loop-and-node-js-event-loop

1.2 nodejs的event-loop

官方文档的event-loop模型如下:
在这里插入图片描述

其中的关键节点说明

  • timers: this phase executes callbacks scheduled by setTimeout() and setInterval(). 此阶段执行setTimeout()和setInterval()里面的回调逻辑
  • pending callbacks: executes I/O callbacks deferred to the next loop iteration. 
    idle, prepare: only used internally. 内部使用的一个状态
  • poll: retrieve new I/O events; execute I/O related callbacks (almost all with the exception of close callbacks, the ones scheduled by timers, and setImmediate()); node will block here when appropriate.
  • check: setImmediate() callbacks are invoked here. 此阶段执行setImmediate()的回调
  • close callbacks: some close callbacks, e.g. socket.on(‘close’, …).

在一个I/O循环中,immediate() 总是比 timeout() 先执行 within an I/O cycle, the
immediate callback is always executed first than timeout callback:

参考:

  • https://hackernoon.com/arthurunderstanding-the-nodejs-event-loop-hin34zp
  • https://nodejs.org/uk/docs/guides/event-loop-timers-and-nexttick/

2. macrotask和microtask

在ES6中macro-task队列又称为ScriptJobs,而micro-task又称PromiseJobs
https://tc39.es/ecma262/#sec-jobs-and-job-queues

每个event loop都有一个macrotask和microtask
Each event loop has a microtask queue and a macrotask queue.

一个microtask是用来放入microstask queue里面的,而不是task queue(对应的是macrotask),参考如下文档
A microtask is a task that is originally to be queued on the microtask queue rather than a task queue. Refer to https://www.w3.org/TR/html51/webappapis.html#microtask-queue.

有2种类型的microtask(微任务)
There are two kinds of microtasks:

  • solitary callback microtasks, such as Promise, 单独的回调,如Promise
  • and compound microtasks, such as Object.observe, MutationObserver and process.nextTick in Node.js. 组合的microtask,如Object.observe, MutationObserver,和nodejs里面的process.nextTick

有如下类型的macrotask(宏任务)
And the macrotask queue mainly contains

  • setTimeout,
  • setInterval,
  • setImmediate,
  • requestAnimationFrame,
  • I/O in Nodejs.

在一个event-loop里面,2种task运行规则如下:
In a event Loop, these two task queues will run in two steps:

  • First, check whether there is a macrotask (call it X) in old macrotask queue ;
  • If X exists and it is running, wait for it to go to the next step until it was complete; otherwise, goto the next step immediately;
  • Second, run all microtasks of the microtask queue;
  • and when run the microtasks, we can still add some more microtaks into the queue, these tasks will also run.

The Call Stack is a fundamental part of the JavaScript language. It is a record-keeping structure that allows us to perform function calls. Each function call is represented as a frame on the Call Stack. This is how the JavaScript engine keeps track of which functions have been called and in what order. The JS engine uses this information to ensure execution picks back up in the right spot after a function returns.

When a JavaScript program first starts executing, the Call Stack is empty. When the first function call is made, a new frame is pushed onto the top of the Call Stack. When that function returns, its frame is popped off of the Call Stack.

The Event Loop is a looping algorithm that processes the Tasks/Microtasks in the Task Queue and Microtask Queue. It handles selecting the next Task/Microtask to be run and placing it in the Call Stack for execution.

The Event Loop algorithm consists of four key steps:

  1. Evaluate Script: Synchronously execute the script as though it were a function body. Run until the Call Stack is empty.
  2. Run a Task: Select the oldest Task from the Task Queue. Run it until the Call Stack is empty.
  3. Run all Microtasks: Select the oldest Microtask from the Microtask Queue. Run it until the Call Stack is empty. Repeat until the Microtask Queue is empty.
  4. Rerender the UI: Rerender the UI. Then, return to step 2. (This step only applies to browsers, not NodeJS).

Let’s model the Event Loop with some JavaScript psuedocode:

while (EventLoop.waitForTask()) {
  const taskQueue = EventLoop.selectTaskQueue();
  if (taskQueue.hasNextTask()) {
    taskQueue.processNextTask();
  }

  const microtaskQueue = EventLoop.microTaskQueue;
  while (microtaskQueue.hasNextMicrotask()) {
    microtaskQueue.processNextMicrotask();
  }

  rerender();
}

参考文档:

  • https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide
  • https://stackoverflow.com/questions/25915634/difference-between-microtask-and-macrotask-within-an-event-loop-context?rq=1
  • https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide/In_depth
  • https://medium.com/dkatalis/eventloop-in-nodejs-macrotasks-and-microtasks-164417e619b9
  • https://stackoverflow.com/questions/46375711/what-is-the-relationship-between-event-loop-and-promise

3. 执行时序

3.1 整体的执行时序

script(主程序代码)—>process.nextTick—>Promises…——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering
setTimeout(function(){console.log(1)},0);

// 异步创建
Promise.resolve().then(function () {
    console.log(22);
  });

// 同步创建
new Promise(function(resolve,reject){
   console.log(2);
   resolve(true);
}).then(function(){console.log(3)
}).then(function(){console.log(4)});

process.nextTick(function(){console.log(5)});

new Promise(function(resolve,reject){
    console.log(7);
    resolve(true);
 });

console.log(6);

class Studuent {
    private mAge:number = 0;

    constructor(age:number) {
        this.mAge = age
        console.log("age");
    }
}

new Studuent(10);

执行结果如下:

2
7
6
age
5
22
3
4
1

说明:

  • 首先执行的是new Promise构造方法里面的代码,按js代码的执行顺序,输出2
  • 然后输出的是7,主要是用来测试new Promise构造方和process.nextTick的顺序
  • 第3个输出的是6,按代码的顺序
  • 第4个输出的是age,主要是用来验证new Promise构造方法,原理都类似,代码按顺序执行过来,执行构造方法里面的逻辑
  • 第5个输出的是5,执行process.nextTick,是在本次代码执行完,准备执行后续event-loop之前执行
  • 第6个输出的是22,走第1个promise
  • 第7个和第8个,依次走后续的promise
  • 第9个输出是1,走的是setTimeout,在下一次macrotask里面执行

再看一个特殊一点的例子

process.nextTick(() => console.log(1));
Promise.resolve().then(() => console.log(2));
Promise.resolve().then(() => {
    console.log(3);
    process.nextTick(() => console.log(4));
    Promise.resolve().then(() => console.log(5));
}).then(() => {
    console.log(6);
})

执行结果

1
2
3
5
6
4

特殊的地方在4,当执行以上代码的时候
When we run the code:

  • callback in process.nextTick is added to process.nextTick queue.
  • callback in promises are added to promises microtask queue.

Event loop executes tasks in following order: process.nextTick queue, promises microtask queue, setTimeout queue, setImmediate queue

  • Event loop will execute callback from process.nextTick queue and prints 1.
  • Event loop will look for promises microtask queue and then processes them. So it prints 2 and 3. On the next line it again adds a callback to process.nextTick queue which will be executed once promises microtask queue is finished.
  • So the next 5 and 6 will be printed. Now promises microtask queue is empty
  • Event loop will again check if there is any handler in process.nextTick queue and will execute them. So it now prints 4.

参考:

  • https://medium.com/dkatalis/eventloop-in-nodejs-macrotasks-and-microtasks-164417e619b9
  • https://medium.com/dkatalis/eventloop-in-nodejs-settimeout-setimmediate-vs-process-nexttick-37c852c67acb
  • https://developer.aliyun.com/article/613411
  • https://github.com/nodejs/help/issues/1789

3.2 process.nextTick和setImmediate

nextTick是在setImmediate之前执行的

在上面的nodejs的event-loop图里面,process.nextTick()里面的回调会在每次event-loop继续执行之前执行
Looking back at our diagram, any time you call process.nextTick() in a given phase, all callbacks passed to process.nextTick() will be resolved before the event loop continues.

使用process.nextTick()方法,可以保证我们的代码可以在用户代码执行完之后且在event-loop事件开始前执行
By using process.nextTick() we guarantee that apiCall() always runs its callback after the rest of the user’s code and before the event loop is allowed to proceed.

function apiCall(arg, callback) {
  if (typeof arg !== 'string')
    return process.nextTick(
      callback,
      new TypeError('argument should be string')
    );
}

关于这2个方法的命名,其实有一些歧义,nodejs的开发也有如下解释:

Note that nextTick is opposite of “in the next tick”. It’s “in the
current tick but after all synchronous code and previously queued
nextTicks finished”.

setImmediate is the opposite of immediately - it runs in the next tick
😅 .

Their naming is pretty confusing. setImmediate is already promisified
and people can use that to await for I/O to run.

参考:

  • https://github.com/nodejs/node/issues/19617
  • https://github.com/nodejs/node/pull/38393
  • https://medium.com/dkatalis/eventloop-in-nodejs-settimeout-setimmediate-vs-process-nexttick-37c852c67acb
  • https://github.com/nodejs/nodejs.dev/blob/main/src/documentation/0030-node-process-nexttick/index.md
  • https://nodejs.org/uk/docs/guides/event-loop-timers-and-nexttick/

3.3 setImmediate()和setTimeout()

2个方法都类似,差异的地方在于他们调用的时机

  • 在非I/O环境下,setTimeout在setImmediate之前执行
  • 在I/O环境下,setImmediate在setTimeout之前执行

在非I/O环境下

// timeout_vs_immediate.js
setTimeout(() => {
  console.log('timeout');
}, 0);

setImmediate(() => {
  console.log('immediate');
});

执行结果是(多次执行的结果是一样的,和里面说的有点不一样,可能是执行环境的原因吧https://nodejs.org/uk/docs/guides/event-loop-timers-and-nexttick/)

$ node timeout_vs_immediate.js
timeout
immediate

在I/O环境下

// timeout_vs_immediate.js
const fs = require('fs');

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

执行结果是:

$ node timeout_vs_immediate.js
immediate
timeout

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/937126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CNN 02(CNN原理)

一、卷积神经网络(CNN)原理 1.1 卷积神经网络的组成 定义 卷积神经网络由一个或多个卷积层、池化层以及全连接层等组成。与其他深度学习结构相比,卷积神经网络在图像等方面能够给出更好的结果。这一模型也可以使用反向传播算法进行训练。相比较其他浅层或深度神经…

Dreamweaver软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 Dreamweaver软件是一款专业的网页开发工具,由Adobe公司开发并广泛应用于Web开发领域。它提供了一站式的网页开发解决方案,包括网页设计、网页编程、网站管理和移动应用开发等功能。 Dreamweaver软件具有…

云计算服务体系-架构真题(十四)

云计算服务体系结构SaaS、PaaS、IaaS相对应分别()。 答案。应用层、平台层、基础设施层 (2022)给定关系模式R(U,F),其中U为属性集,F是U的一组函数依赖,那么函数依赖的公理系统(Armstrong)中分解规则是指(&…

API管理测试 - 最佳实践和关键要素

什么是API管理测试? API管理测试是在软件开发和集成功能中对应用程序接口(API)进行测试和验证的过程。它涵盖了测试API的功能、性能、安全性以及与其他系统的交互。API管理测试对于确保API的正确运行和稳定性非常重要。 ​ 为什么API管理测…

39.RESTful案例

RESTful案例 准备环境 Employee.java public class Employee {private Integer id;private String lastName;private String email;//1 male, 0 femaleprivate Integer gender; } //省略get、set和构造方法EmployeeDao.java package com.atguigu.SpringMVC.dao;import com.…

【springboot】WebScoket双向通信:

文章目录 一、介绍:二、案例:三、使用:【1】导入WebSocket的maven坐标【2】导入WebSocket服务端组件WebSocketServer,用于和客户端通信【3】导入配置类WebSocketConfiguration,注册WebSocket的服务端组件【4】导入定时…

神经网络学习小记录75——Keras设置随机种子Seed来保证训练结果唯一

神经网络学习小记录75——Keras设置随机种子Seed来保证训练结果唯一 学习前言为什么每次训练结果不同什么是随机种子训练中设置随机种子 学习前言 好多同学每次训练结果不同,最大的指标可能会差到3-4%这样,这是因为随机种子没有设定导致的,我…

Django项目第一次打开加载不出css文件

你需要找到setting.py如下部分 修改你存放css文件和js等文件的目录 指定正确,本地就能跑了

[好书推荐] 之 <趣化计算机底层技术>

趣化计算机底层技术 底层技术优势购买 底层技术 相信很多老铁跟我一样, 在深入了解底层技术的时候 — — 就很头大 很多书籍看上去跟一个 老学究 一样, 说的话不是我们这些小白看的懂得… 看不懂就会 打击我们的自信心我们就有可能找一堆理由去玩(理所应当地去玩的那一种, 反…

如何使用腾讯云服务器搭建网站?新手建站教程

使用腾讯云服务器搭建网站全流程,包括轻量应用服务器和云服务器CVM建站教程,轻量可以使用应用镜像一键建站,云服务器CVM可以通过安装宝塔面板的方式来搭建网站,腾讯云服务器网分享使用腾讯云服务器建站教程,新手站长搭…

【教程分享】Docker搭建Zipkin,实现数据持久化到MySQL、ES

1 拉取镜像 指定版本,在git查看相应版本,参考: https://github.com/openzipkin/zipkin 如2.21.7 docker pull openzipkin/zipkin:2.21.7 2 启动 Zipkin默认端口为9411。启动时通过-e server.portxxxx设置指定端口 docker run --name zi…

04_21 slab分配器 分配对象实战

目的 ( slab块分配器分配内存)&#xff0c;编写个内核模块&#xff0c;创建名称为 “mycaches"的slab描述符&#xff0c;小为40字节, align为8字节&#xff0c; flags为0。 从这个slab描述符中分配个空闲对象。 代码大概 内核模块中 #include <linux/version.h>…

C++ 网络编程项目fastDFS分布式文件系统(九)总结

1. Location语法 1. 语法规则 location [ |~|~ * |^~ ] /uri/ { … } 正则表达式中的特殊字符 : - . () {} [] * ? 2. Location 优先级说明 在 nginx 的 location 和配置中 location 的顺序没有太大关系。 与 location 表达式的类型有关。 相同类型的表达式&a…

android系统启动流程之zygote如何创建SystemServer进程

SystemServer:是独立的进程&#xff0c;主要工作是管理服务的&#xff0c;它将启动大约90种服务Services. 它主要承担的职责是为APP的运行提供各种服务&#xff0c;像AMS,WMS这些服务并不是一个独立的进程&#xff0c; 它们其实都是SystemServer进程中需要管理的的众多服务之一…

CDN/DCDN(全站加速)排查过程中如何获取Eagle ID/UUID

目录 前言1.通过浏览器直接访问文件时获取Request ID 前言 阿里云CDN/DCDN(全站加速)为接收到的每个请求分配唯一的服务器请求ID&#xff0c;作为关联各类日志信息的标识符。当您在使用CDN/DCDN(全站加速)过程中遇到错误且希望阿里云技术支持提供协助时&#xff0c;需要提交失…

UnitTest笔记: 拓展库DDT的使用

DDT (Data-Drivers- Tests) 允许使用不同的测试数据运行同一个测试用例&#xff0c;展示为不同的测试用例。 第一步&#xff1a; pip安装 ddt 第二步&#xff1a; 创建test_baidu_ddt.py 1. 测试类要使用ddt 修饰 2. 不同形式的参数化&#xff1a; 列表&#xff0c;字典&a…

Java常见的排序算法

排序分为内部排序和外部排序&#xff08;外部存储&#xff09; 常见的七大排序&#xff0c;这些都是内部排序 。 1、插入排序&#xff1a;直接插入排序 1、插入排序&#xff1a;每次将一个待排序的记录&#xff0c;按其关键字的大小插入到前面已排序好的记录序列 中的适当位置…

新版-C语言学生信息管理系统

拥有基本的学生信息系统的功能, 功能点如下所示: 1.添加学生信息 2.修改学生信息 3.删除学生信息 4.查看学生信息 5.搜索学生信息 6.查看系统学生总人数 7.学生信息排序 8.保存学生信息(保存在D:/students.txt) 9.导入学生信息(导入D:/students.txt文件中的信息) 主界面 1.添加…

人工智能轨道交通行业周刊-第57期(2023.8.21-8.27)

本期关键词&#xff1a;桥梁养护、智慧天路、列车通信网络、AIGC产业报告、价值对齐、异常检测 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通RailMet…

浅谈 Java 中的 Lambda 表达式

更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验 Lambda 表达式是一种匿名函数&#xff0c;它可以作为参数传递给方法或存储在变量中。在 Java8 中&#xff0c;它和函数式接口一起&#xff0c;共同构建了函数式编程的框架。 什么是函数式编程 函数式编程是…