Node.js事件循环究竟是什么?

d51*_*512 63 message-queue event-loop node.js

我已经越来越多地进入node.js体系结构的内部,我看到的一个术语是"tick",如"事件循环的下一个tick"或函数nextTick().

我没有看到的是对什么是"滴答"的确切定义.基于各种文章(例如这篇文章),我已经能够将一个概念拼凑在一起,但我不确定它是多么准确.

我能获得node.js事件循环滴答的精确详细描述吗?

jos*_*736 128

请记住,虽然JavaScript是单线程的,但所有节点的I/O和对本机API的调用都是异步的(使用特定于平台的机制),或者在单独的线程上运行.(这都是通过libuv处理的.)

因此,当套接字或本机API函数返回时有可用数据时,我们需要一种同步方式来调用对刚刚发生的特定事件感兴趣的JavaScript函数.

从原本事件发生的线程调用JS函数是不安全的,原因与您在常规多线程应用程序中遇到的原因相同 - 竞争条件,非原子内存访问等等.

所以我们所做的是以线程安全的方式将事件放在队列中.在过度简化的伪代码中,类似于:

lock (queue) {
    queue.push(event);
}
Run Code Online (Sandbox Code Playgroud)

然后,回到主JavaScript 线程(但在事物的C方面),我们做类似的事情:

while (true) {
    // this is the beginning of a tick

    lock (queue) {
        var tickEvents = copy(queue); // copy the current queue items into thread-local memory
        queue.empty(); // ..and empty out the shared queue
    }

    for (var i = 0; i < tickEvents.length; i++) {
        InvokeJSFunction(tickEvents[i]);
    }

    // this the end of the tick
}
Run Code Online (Sandbox Code Playgroud)

while (true)(这实际上不存在于节点的源代码;这是纯说明性)表示事件循环.内部for为队列中的每个事件调用JS函数.

这是一个勾号:同步调用与任何外部事件关联的零个或多个回调函数.一旦队列清空并且最后一个函数返回,则勾选结束.我们回到开头(下一个tick)并检查在JavaScript运行时从其他线程添加到队列的事件.

什么可以添加到队列?

  • process.nextTick
  • setTimeout/setInterval
  • I/O(东西fs,net等等)
  • crypto处理器密集型功能,如加密流,pbkdf2和PRNG(实际上是......的一个例子)
  • 任何使用libuv工作队列进行同步C/C++库调用的本机模块都是异步的

  • 是的,你钉了这个.复制队列并运行副本上的所有事件是我特别想知道的.虽然现在很有意义.谢谢. (2认同)
  • 我想在0.10.x中添加`setImmediate`也会将函数排入队列. (2认同)
  • 打勾是否意味着事件循环阶段? (2认同)

Obi*_*ill 7

                                                              \n                          \xe2\x94\x8c \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x90             \n                              THE EVENT LOOP                  \n                          \xe2\x94\x94 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x80 \xe2\x94\x98             \n                                                              \n                     \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90        \n                     \xe2\x94\x82             poll              \xe2\x94\x82        \n                  \xe2\x94\x8c\xe2\x94\x80\xe2\x96\xb6\xe2\x94\x82                               \xe2\x94\x82\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90     \n                  \xe2\x94\x82  \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98  \xe2\x94\x82     \n                  \xe2\x94\x82                  \xe2\x94\x82                 tick   \n                  \xe2\x94\x82  \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x96\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90  \xe2\x94\x82     \n                  \xe2\x94\x82  \xe2\x94\x82             check             \xe2\x94\x82  \xe2\x94\x82     \n                  \xe2\x94\x82  \xe2\x94\x82                               \xe2\x94\x82\xe2\x97\x80\xe2\x94\x80\xe2\x94\x98     \n                  \xe2\x94\x82  \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98        \n                  \xe2\x94\x82                  \xe2\x94\x82                        \n                  \xe2\x94\x82  \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x96\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90        \n                  \xe2\x94\x82  \xe2\x94\x82        close callbacks        \xe2\x94\x82        \n                  \xe2\x94\x82  \xe2\x94\x82                               \xe2\x94\x82        \n                loop \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98        \n                  \xe2\x94\x82                  \xe2\x94\x82                        \n                  \xe2\x94\x82  \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x96\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90        \n                  \xe2\x94\x82  \xe2\x94\x82            timers             \xe2\x94\x82        \n                  \xe2\x94\x82  \xe2\x94\x82                               \xe2\x94\x82        \n                  \xe2\x94\x82  \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98        \n                  \xe2\x94\x82                  \xe2\x94\x82                        \n                  \xe2\x94\x82  \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x96\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90        \n                  \xe2\x94\x82  \xe2\x94\x82       pending callbacks       \xe2\x94\x82        \n                  \xe2\x94\x82  \xe2\x94\x82                               \xe2\x94\x82        \n                  \xe2\x94\x82  \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98        \n                  \xe2\x94\x82                  \xe2\x94\x82                        \n                  \xe2\x94\x82  \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x96\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90        \n                  \xe2\x94\x82  \xe2\x94\x82         idle, prepare         \xe2\x94\x82        \n                  \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x82                               \xe2\x94\x82        \n                     \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98        \n
Run Code Online (Sandbox Code Playgroud)\n

事件循环(在 Node.js 中)是一种执行模型,其中脚本的各个方面根据定义的时间表以循环方式执行。

\n

它[事件循环]由多个阶段组成(如上图所示)。每个阶段包含 (1) 一个调用堆栈和 (2) 一个回调队列。调用堆栈是执行代码的地方(以 LIFO 为基础),而回调队列是安排代码的地方(以 FIFO 为基础),以便稍后放置在调用堆栈中执行。

\n

这个回调队列可以分为2个队列:一个microTask队列和一个macroTask队列。微任务(一旦调度)是在当前阶段的当前运行脚本之后立即执行的任务,而宏任务(一旦调度)是在该阶段的下一个循环中执行的任务(在该阶段的任何微任务之后)。

\n

事件循环在所有阶段中重复运行,直到没有更多工作要做。每个周期(通过所有阶段)可以称为循环,而给定队列中脚本的每次完整调用可以称为tick

\n

这种tick通常会从一个阶段发生到另一个阶段,但是当microTask和macroTask队列都不为空时,tick可能发生在一个阶段内,例如,当在运行脚本中解决Promise时,其方法将then项目添加到microTask队列中。

\n

当您编写代码(例如在mycode.js文件中)然后调用它(使用node mycode.js)时,该代码将根据其编写方式使用事件循环执行。

\n

这是一个示例脚本:

\n
process.nextTick(function() {\n  console.log(\'next tick - 1 [scheduled from poll]\');\n});\n\nconsole.log(\'poll phase - 1\');\n\nsetImmediate(function() {\n  console.log(\'check phase - 1\');\n\n  process.nextTick(function() {\n    console.log(\'next tick - 2 [scheduled from check]\');\n  });\n\n  Promise.resolve()\n    .then(function() {\n      console.log(`check phase - 1.1 [microTask]`);\n    })\n    .then(function() {\n      console.log(`check phase - 1.2 [microTask]`);\n    })\n    .then(function() {\n      setTimeout(function() {\n        console.log(\'timers phase [scheduled from Promise in check]\');\n      });\n      process.nextTick(function() {\n        console.log(\'next tick - 3 [scheduled from Promise in check]\');\n      });\n    });\n\n  console.log(\'check phase - 2\');\n});\n\nsetTimeout(function() {\n  console.log(\'timers phase - 1\');\n\n  setImmediate(function() {\n    console.log(\'check phase [scheduled from timers]\');\n  });\n\n  Promise.resolve()\n    .then(function() {\n      console.log(\'timers phase - 1.1 [microTask]\');\n    })\n    .then(function() {\n      console.log(\'timers phase - 1.2 [microTask]\');\n    })\n    .then(function() {\n      setTimeout(function() {\n        console.log(\'timers phase [scheduled from Promise in timers]\');\n      });\n    });\n});\n\nprocess.nextTick(function() {\n  console.log(\'next tick - 4 [scheduled from poll]\');\n});\n\nconsole.log(\'poll phase - 2\');\n
Run Code Online (Sandbox Code Playgroud)\n

将其复制(或输入)到 .js 文件中,然后使用 .js 调用它node

\n

您应该得到以下输出:

\n
poll phase - 1\npoll phase - 2\nnext tick - 1 [scheduled from poll]\nnext tick - 4 [scheduled from poll]\ncheck phase - 1\ncheck phase - 2\nnext tick - 2 [scheduled from check]\ncheck phase - 1.1 [microTask]\ncheck phase - 1.2 [microTask]\nnext tick - 3 [scheduled from Promise in check]\ntimers phase - 1\ntimers phase - 1.1 [microTask]\ntimers phase - 1.2 [microTask]\ntimers phase [scheduled from Promise in check]\ncheck phase [scheduled from timers]\ntimers phase [scheduled from Promise in timers]\n
Run Code Online (Sandbox Code Playgroud)\n
\n

笔记:使用 Node.js 版本 16.15.0

\n
\n

在解释之前,这里有一些需要记住的规则:

\n
    \n
  • setImmediate安排脚本在事件循环的下一个检查阶段运行(在宏任务队列中)
  • \n
  • setTimeout安排脚本在事件循环的下一个计时器阶段运行(在宏任务队列中)
  • \n
  • Process.nextTick安排脚本在下一个时钟周期之前运行,即 (1) 在当前脚本运行之后但在 microTask 队列运行之前 [如果所述队列不为空],或者 (2) 在事件循环从一个阶段遍历到下一阶段之前[如果 microTask 队列为空]
  • \n
  • Promise.prototype.then安排脚本在当前微任务队列中运行,即在当前脚本之后,但在为下一阶段安排的脚本之前
  • \n
  • microTask队列在macroTask队列之前运行
  • \n
\n

以下是事件时间线形式的解释:

\n

A. 从​​投票阶段(循环 1)开始

\n
    \n
  1. console.log(\'poll phase - 1\')console.log(\'poll phase - 2\')是同步代码,将在当前阶段立即运行
  2. \n
  3. console.log(\'next tick - 1 [scheduled from poll]\')并被console.log(\'next tick - 4 [scheduled from poll]\')安排process.nextTick在下一个时钟周期之前运行,即在检查阶段之前运行(因为 microTask 队列中没有任何内容)。
  4. \n
  5. (第 7 行)上的回调计划在检查阶段setImmediate运行
  6. \n
  7. (第 33 行)上的回调计划在计时器阶段setTimeout运行
  8. \n
\n

B. 检查阶段之前(循环 1)\n5. console.log(\'next tick - 1 [scheduled from poll]\')并被console.log(\'next tick - 4 [scheduled from poll]\')执行

\n

C. 从检查阶段(循环 1)开始\n6。console.log(\'check phase - 1\')console.log(\'check phase - 2\')[来自先前由setImmediate(第 7 行)安排的回调] 会立即执行,因为它们是同步的\n7。console.log(\'next tick - 2 [scheduled from check]\')由 \n8 安排process.nextTick。第 15、18 和 21 行的回调计划在 microTask 队列中运行。\n9. console.log(\'next tick - 2 [scheduled from check]\')被执行(因为这是在下一个tick之前,即在当前脚本之后但在microTask队列之前)\n10. 第 15 行和第 18 行的回调被执行(因为 microTask 是在运行脚本之后立即执行的)\n11. 第 21 行的回调被执行,并安排 (1)console.log(\'timers phase [scheduled from Promise in check]\')在下一个计时器阶段运行,(2)console.log(\'next tick - 3 [scheduled from Promise in check]\')在下一个时钟周期之前运行,即从当前阶段 ( check ) 遍历到下一个活动阶段 (计时器)之前

\n

D. 定时器阶段之前(循环 1)\n12. console.log(\'next tick - 3 [scheduled from Promise in check]\')被执行

\n

E. 来自定时器阶段(循环 1)\n13。console.log(\'timers phase - 1\')已执行\n14. (第 36 行)安排其回调在下一个检查setImmediate阶段运行\n15。(第 40 行)安排三个回调在微任务队列中运行\n16。并按计划于 15.\n17 执行。被执行。之前是由(22 号线)安排的。它现在正在运行(在上面16中的代码之后),因为它是一个macroTask(所以它在microTask队列运行之后运行)Promiseconsole.log(\'timers phase - 1.1 [microTask]\')console.log(\'timers phase - 1.2 [microTask]\')console.log(\'timers phase [scheduled from Promise in check]\')setTimeout

\n

E. 从下一个检查阶段(循环 2)开始\n18。console.log(\'check phase [scheduled from timers]\')被执行。它之前被安排在计时器阶段(循环 1)setImmediate(第 36 行)

\n

F. 从下一个定时器阶段开始(循环 2)\n19。console.log(\'timers phase [scheduled from Promise in timers]\')被执行。它之前被安排在计时器阶段(循环 1)setTimeout(第 48 行)

\n

参考

\n\n


Par*_*arm 6

对于那些刚接触JavaScript的人来说,答案更简单:

首先要理解的是JavaScript是一个"单线程环境".这是指JavaScript在单个线程上从"事件循环"一次执行一个代码块的行为.下面是从Kyle Simpson的书ydkJS中获取的事件循环的基本实现,然后是一个解释:

// `eventLoop` is an array that acts as a queue (first-in, first-out)
var eventLoop = [ ];
var event;

// keep going "forever"
while (true) {
    // perform a "tick"
    if (eventLoop.length > 0) {
        // get the next event in the queue
        event = eventLoop.shift();

        // now, execute the next event
        try {
            event();
        }
        catch (err) {
            reportError(err);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

第一个while循环模拟事件循环.tick是从"事件循环队列"中取出事件并执行所述事件.

请参阅"Josh3796"的回复,以获得有关事件出列和执行情况的更详细说明.

另外,我建议那些有兴趣深入理解JavaScript的人阅读Kyle Simpson的书.它是完全免费和开源的,可以在以下链接找到:https: //github.com/getify/You-Dont-Know-JS

我引用的具体部分可以在这里找到:https://github.com/getify/You-Dont-Know-JS/blob/master/async%20%26%20performance/ch1.md#event-loop

  • 这让我很困惑,因为这个答案让我觉得只有一个队列。并且只有在该队列中,排队的一个事件才被视为一个时钟周期。从互联网的其他一些来源,我看到勾选意味着处理单个阶段队列中的所有事件。 (5认同)