nodejs(libuv)事件循环是否在移动到下一个阶段(队列)之前执行一个阶段(队列)中的所有回调或以循环方式执行运行?

Does the nodejs (libuv) event loop execute all the callbacks in one phase (queue) before moving to the next or run in a round robin fashion?

我正在研究Node.js中libuv提供的事件循环。我遇到了 following blog by Deepal Jayasekara 并且还在 youtube 上看到了 Bert Belder 和 Daniel Khan 的解释。

有一点我不太清楚——根据我的理解,事件循环在进入另一个阶段之前会处理一个阶段的所有项目。因此,如果是这种情况,如果 setTimeout 阶段不断向其添加回调,我应该能够阻止事件循环。

然而,当我试图复制它时,它并没有发生。以下是代码:

var http = require('http');

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.write('Hello World!');
  console.log("Response sent");
  res.end();
}).listen(8081);


setInterval(() => {
  console.log("Entering for loop");
// Long running loop that allows more callbacks to get added to the setTimeout phase before this callback's processing completes
 for (let i = 0; i < 7777777777; i++); 
 console.log("Exiting for loop");
}, 0);

事件循环似乎 运行 以循环方式进行。它首先执行在我向服务器发送请求之前添加的回调,然后处理请求,然后继续回调。感觉单队列是运行ning。 据我所知,没有一个队列,所有过期的计时器回调都应该在进入下一阶段之前首先执行。因此,上面的代码片段不应该能够 return Hello World 响应。

对此可能的解释是什么? 谢谢

如果查看 libuv 本身,您会发现事件循环中 运行 定时器的操作部分是函数 uv_run_timers()

void uv__run_timers(uv_loop_t* loop) {
  struct heap_node* heap_node;
  uv_timer_t* handle;

  for (;;) {
    heap_node = heap_min(timer_heap(loop));
    if (heap_node == NULL)
      break;

    handle = container_of(heap_node, uv_timer_t, heap_node);
    if (handle->timeout > loop->time)
      break;

    uv_timer_stop(handle);
    uv_timer_again(handle);
    handle->timer_cb(handle);
  }
}

它的工作方式是事件循环在当前时间设置一个时间标记,然后在不更新循环时间的情况下依次处理该时间到期的所有计时器。因此,这将触发所有已经超时的计时器,但不会触发任何在处理已经到期的计时器时到期的新计时器。

这会导致更公平的调度,因为它会运行所有到期的计时器,然后继续运行事件循环中的其余类型的事件,然后返回来执行更多到期的计时器。这不会处理在此事件循环周期开始时未到期但在处理其他计时器时到期的任何计时器。因此,您会看到您询问的行为。

使用此代码从事件循环的 main part 调用上述函数:

int uv_run(uv_loop_t *loop, uv_run_mode mode) {
  DWORD timeout;
  int r;
  int ran_pending;

  r = uv__loop_alive(loop);
  if (!r)
    uv_update_time(loop);

  while (r != 0 && loop->stop_flag == 0) {
    uv_update_time(loop);                    <==  establish loop time
    uv__run_timers(loop);                    <==  process only timers due by that loop time

    ran_pending = uv_process_reqs(loop);
    uv_idle_invoke(loop);
    uv_prepare_invoke(loop);

 .... more code here

}

注意在调用 uv__run_timers() 之前调用 uv_update_time(loop)。这设置了 uv__run_timers() 引用的计时器。这是 uv_update_time()the code

void uv_update_time(uv_loop_t* loop) {
  uint64_t new_time = uv__hrtime(1000);
  assert(new_time >= loop->time);
  loop->time = new_time;
}

来自 docs

when the event loop enters a given phase, it will perform any operations specific to that phase, then execute callbacks in that phase's queue until the queue has been exhausted or the maximum number of callbacks has executed. When the queue has been exhausted or the callback limit is reached, the event loop will move to the next phase, and so on.

也来自docs,

When delay is larger than 2147483647 or less than 1, the delay will be set to 1

现在,当您 运行 您的代码片段发生以下事情时,

  1. 脚本执行开始,回调注册到特定阶段。此外,正如文档所建议的那样,setInterval 延迟被隐式转换为 1 秒。
  2. 1 秒后,您的 setInterval 回调将被执行,它将阻塞 eventloop 直到所有迭代完成。同时,至少在循环终止之前,eventloop 不会收到任何传入请求的通知。
  3. 所有迭代完成后,超时为 1 秒,轮询阶段将执行您的 HTTP 请求回调(如果有)。
  4. 回到第 2 步。