node mysql 事件循环_NodeJs 的 Event loop 事件循环机制详解

本文详细介绍了Node.js的事件循环(Event Loop)机制,包括事件循环的各个阶段:timers、IO/callbacks、idle, prepare、poll、check和close callbacks。重点阐述了各阶段的功能、工作原理以及它们之间的交互,特别是timer阶段与poll阶段的关系,以及setImmediate()与setTimeout()的区别和使用场景。此外,还提到了process.nextTick()的执行时机及其可能带来的问题。" 133551966,19673579,VLOOKUP函数:0与1的匹配原理与编程实现,"['Excel函数', '编程', '数据处理']
摘要由CSDN通过智能技术生成

什么是事件轮询

事件循环是 Node.js 处理非阻塞 I/O 操作的机制——尽管 JavaScript 是单线程处理的——当有可能的时候,它们会把操作转移到系统内核中去。

下面的图表显示了事件循环的概述以及操作顺序。

┌───────────────────────────┐

┌─>│ timers │

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

│ │ IO / callbacks │

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

│ │ idle, prepare │

│ └─────────────┬─────────────┘ ┌───────────────┐

│ ┌─────────────┴─────────────┐ │ incoming: │

│ │ poll │

│ └─────────────┬─────────────┘ │ data, etc. │

│ ┌─────────────┴─────────────┐ └───────────────┘

│ │ check │

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

└──┤ close callbacks │

└───────────────────────────┘

三大关键阶段

timer:执行定时器时,如 setTimeout、setInterval,在 timers 阶段执行

poll:异步操作,比如文件I/O,网络I/O等,通过'data'、 'connect'等事件通知 JS 主线程并执行回调的,此阶段就是 poll 轮询阶段

check:这是一个比较简单的阶段,直接执行 setImmdiate 的回调。

注意,若 2 阶段结束后,当前存在到时间的定时器,那么拿出来执行,eventLoop 将再回到 timer 阶段

阶段流程概述

timers: 本阶段执行已经安排的 setTimeout() 和 setInterval() 的回调函数

IO / callbacks: 执行 I/O 异常的回调,如TCP 连接遇到 ECONNREFUSED

idle, prepare: 仅系统内部使用,只是表达空闲、预备状态(第2阶段结束,poll 未触发之前)

poll: 检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数),node 将在此处阻塞。

check: setImmediate() 回调函数在这里执行.

close callbacks: 一些准备关闭的回调函数,如:socket.on('close', ...)

在每次运行的事件循环之间,Node.js 检查它是否在等待任何异步 I/O 或计时器,如果没有的话,则关闭干净。

timers

timers 指定 可执行所提供回调 的 时间阈值,poll 阶段 控制何时定时器执行。

一旦 poll queue 为空,事件循环将检查 已达到时间阈值的timer计时器。如果一个或多个计时器已准备就绪,则事件循环将回到 timer 阶段以执行这些计时器的回调

pending callbacks

此阶段对某些系统操作(如 TCP 错误类型)执行回调。例如,如果 TCP 套接字在尝试连接时接收到 ECONNREFUSED,则某些 *nix 的系统希望等待报告错误。这将被排队以在 pending callbacks 阶段执行。

poll

轮询 阶段有两个重要的功能:

计算应该阻塞和 poll I/O 的时间。

然后,处理 poll 队列里的事件。

当事件循环进入 poll阶段且 timers scheduled,将发生以下两种情况之一:

if the poll queue is not empty, 事件循环将循环访问其回调队列并同步执行它们,直到队列已用尽,或者达到了与系统相关的硬限制

If the poll queue is empty,还有两件事发生

如果脚本已按 setImmediate() 排定,则事件循环将结束 轮询 阶段,并继续 检查 阶段以执行这些计划脚本。

如果脚本尚未按 setImmediate()排定,则事件循环将等待回调添加到队列中,然后立即执行。

一旦 poll queue 为空,事件循环将检查 已达到时间阈值的timer计时器。如果一个或多个计时器已准备就绪,则事件循环将回到 timer 阶段以执行这些计时器的回调。

check

通常,在执行代码时,事件循环最终会命中轮询阶段,等待传入连接、请求等。但是,如果回调已计划为 setImmediate(),并且轮询阶段变为空闲状态,则它将结束并继续到检查阶段而不是等待轮询事件。

setImmediate() 实际上是一个在事件循环的单独阶段运行的特殊计时器。它使用一个 libuv API 来安排回调在 poll 阶段完成后执行。

close callbacks

如果套接字或处理函数突然关闭(例如 socket.destroy()),则'close' 事件将在这个阶段发出。否则它将通过 process.nextTick() 发出。

setImmediate() 对比 setTimeout()

setImmediate() 和 setTimeout() 很类似,但何时调用行为完全不同。

setImmediate() 设计为在当前 轮询 阶段完成后执行脚本。

setTimeout() 计划在毫秒的最小阈值经过后运行的脚本。

执行计时器的顺序将根据调用它们的上下文而异,如果二者都从主模块内调用,则计时将受进程性能的约束,两个计时器的顺序是非确定性的。

// timeout_vs_immediate.js

setTimeout(() => {

console.log('timeout');

}, 0);

setImmediate(() => {

console.log('immediate');

});

$ node timeout_vs_immediate.js

timeout

immediate

$ node timeout_vs_immediate.js

immediate

timeout

但是,如果你把这两个函数放入一个 I/O 循环内调用,setImmediate 总是被优先调用:

// 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

$ node timeout_vs_immediate.js

immediate

timeout

使用 setImmediate() 超过 setTimeout() 的主要优点是 setImmediate() 在任何计时器(如果在 I/O 周期内)都将始终执行,而不依赖于存在多少个计时器。

process.nextTick()

process.nextTick() 在技术上不是事件循环的一部分,无论事件循环的当前阶段如何,都将在当前操作完成后处理 nextTickQueue。这里的一个操作被视作为一个从 C++ 底层处理开始过渡,并且处理需要执行的 JavaScript 代码。

回顾我们的关系图,任何时候在给定的阶段中调用 process.nextTick(),所有传递到 process.nextTick() 的回调将在事件循环继续之前得到解决。这可能会造成一些糟糕的情况, 因为它允许您通过进行递归 process.nextTick() 来“饿死”您的 I/O 调用,阻止事件循环到达 轮询 阶段。

一个题目

// test.js

process.nextTick(function() {

console.log('next tick');

});

setTimeout(function() {

console.log('settimeout');

});

(async function() {

console.log('async promise');

})();

setImmediate(function() {

console.log('setimmediate');

});

$ node test.js

async promise

next tick

settimeout

setimmediate

没有await,async那句其实是同步执行的,故而第一句输出。

next tick 在任何事件循环阶段继续之前得到解决,故而第二句

setTimeout 在主线程中与 setImmediate 的执行顺序是非确定性的

// test.js

setTimeout(function () {

process.nextTick(function() {

console.log('next tick');

});

setTimeout(function() {

console.log('settimeout');

});

(async function() {

console.log('async promise');

})();

setImmediate(function() {

console.log('setimmediate');

});

})

$ node test.js

async promise

next tick

setimmediate

settimeout

setimmediate 与 settimeout 放入一个 I/O 循环内调用,则 setImmediate 总是被优先调用

node >= 11 ?

setTimeout(()=>{

console.log('timer1')

setImmediate(function () { console.log('immd 1'); })

Promise.resolve().then(function() {

console.log('promise1')

})

}, 0)

setTimeout(()=>{

console.log('timer2')

setImmediate(function () { console.log('immd 2'); })

Promise.resolve().then(function() {

console.log('promise2')

})

}, 0)

在 node 11 及以上版本打印得

timer1

promise1

timer2

promise2

immd 1

immd 2

在 node 版本为 8.11.2 打印

timer1

timer2

promise1

promise2

immd 1

immd 2

这是因为 < 11 得版本中

若第一个定时器任务出队并执行完,发现队首的任务仍然是一个定时器,那么就将微任务暂时保存,直接去执行新的定时器任务,当新的定时器任务执行完后,再一一执行中途产生的微任务。

nodejs 和 浏览器关于eventLoop的主要区别

两者最主要的区别在于浏览器中的微任务是在每个相应的宏任务中执行的,而nodejs中的微任务是在不同阶段之间执行的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值