为什么在此示例中调用 setTimeout 会解除事件循环的阻塞?

发布于 2025-01-19 04:27:27 字数 3128 浏览 0 评论 0原文

以下代码来自此媒体 文章

const fastify = require("fastify")({ logger: true });
const crypto = require("crypto");
const randomstring = require("randomstring");

// Declare healthcheck ping-pong
fastify.get("/ping", (_request, reply) => {
  reply.send({ data: "pong" });
});

fastify.get("/event-loop-blocking-operation", async (_request, reply) => {
  let hash = crypto.createHash("sha256");
  const numberOfHasUpdates = 10e6;

  for (let iter = 0; iter < numberOfHasUpdates; iter++) {
    hash.update(randomstring.generate());
  }
  reply.send({ data: "Finished doing long task" });
});

fastify.get("/event-loop-nonblocking-operation", async (_request, reply) => {
  const hash = crypto.createHash("sha256");
  const numberOfHasUpdates = 10e6;

  const breathSpace = async (delayInS) =>
    new Promise((resolve, reject) => {
      setTimeout(() => resolve(), delayInS * 1000);
    });

  for (let iter = 0; iter < numberOfHasUpdates; iter++) {
    const hashed = hash.update(randomstring.generate());
    await breathSpace(0);
  }
  reply.send({ data: "Finished long process" });
});

// Run the server!
const port = 3000;
fastify.listen(port, (err, address) => {
  if (err) {
    fastify.log.error(err);
    process.exit(1);
  }
  fastify.log.info(`server listening on ${address}`);
});

情况 1

#in Terminal 1
$ node blocking_event_loop.js

#in Terminal 2
#calls 127.0.0.1:3000/ping every one second
#keeps printing {data: "pong"} in terminal
$ while  true; do date; curl 127.0.0.1:3000/ping; sleep 1; echo \n; done;

#in Terminal 3
#blocks the event loop
$ date && curl 127.0.0.1:3000/event-loop-blocking-operation && date

调用最后一个命令后,事​​件循环被阻止,终端 2 停止打印。

情况 2

#in Terminal 1
$ node blocking_event_loop.js

#in Terminal 2
#calls 127.0.0.1:3000/ping every one second
#keeps printing {data: "pong"} in terminal
$ while  true; do date; curl 127.0.0.1:3000/ping; sleep 1; echo \n; done;

#in Terminal 3
#does not block the event loop
$ date && curl 127.0.0.1:3000/event-loop-nonblocking-operation && date

在第二种情况下,调用最后一个命令后,终端 2 继续打印 {data: "pong"} 以及 /event-loop 中定义的操作-blocking-operation 的路由处理程序继续执行其正在执行的操作。

在循环中调用 await BreathSpace(0) 为何会以非阻塞方式运行该操作?

据我所知,当通过curl调用/event-loo-nonblocking-operation端点时:

  1. /event-loop-nonblocking-operation的相应路由处理程序被排队到I/ O 回调队列 也称为轮询队列
  2. 路由处理程序从轮询队列中出队并推送到调用堆栈
  3. 路由处理程序的代码逐行运行 3.1 调用hash.update方法(在for循环的每次迭代中) 3.2 调用 await BreathSpace(0) (在 for 循环的每次迭代中) 3.2.1 (解决、拒绝)=> setTimeout(() =>solve(), 0) 被排队到计时器队列

现在考虑事件循环没有被阻塞,这是否意味着,即使循环的第一次迭代没有完成,事件循环也会改变它的相位轮询计时器来处理解决承诺的 setTimeout?它如何在不阻塞的情况下执行for循环?

The following code is from this medium article

const fastify = require("fastify")({ logger: true });
const crypto = require("crypto");
const randomstring = require("randomstring");

// Declare healthcheck ping-pong
fastify.get("/ping", (_request, reply) => {
  reply.send({ data: "pong" });
});

fastify.get("/event-loop-blocking-operation", async (_request, reply) => {
  let hash = crypto.createHash("sha256");
  const numberOfHasUpdates = 10e6;

  for (let iter = 0; iter < numberOfHasUpdates; iter++) {
    hash.update(randomstring.generate());
  }
  reply.send({ data: "Finished doing long task" });
});

fastify.get("/event-loop-nonblocking-operation", async (_request, reply) => {
  const hash = crypto.createHash("sha256");
  const numberOfHasUpdates = 10e6;

  const breathSpace = async (delayInS) =>
    new Promise((resolve, reject) => {
      setTimeout(() => resolve(), delayInS * 1000);
    });

  for (let iter = 0; iter < numberOfHasUpdates; iter++) {
    const hashed = hash.update(randomstring.generate());
    await breathSpace(0);
  }
  reply.send({ data: "Finished long process" });
});

// Run the server!
const port = 3000;
fastify.listen(port, (err, address) => {
  if (err) {
    fastify.log.error(err);
    process.exit(1);
  }
  fastify.log.info(`server listening on ${address}`);
});

case 1

#in Terminal 1
$ node blocking_event_loop.js

#in Terminal 2
#calls 127.0.0.1:3000/ping every one second
#keeps printing {data: "pong"} in terminal
$ while  true; do date; curl 127.0.0.1:3000/ping; sleep 1; echo \n; done;

#in Terminal 3
#blocks the event loop
$ date && curl 127.0.0.1:3000/event-loop-blocking-operation && date

After calling the last command, the event loop gets blocked and Terminal 2 stops printing.

case 2

#in Terminal 1
$ node blocking_event_loop.js

#in Terminal 2
#calls 127.0.0.1:3000/ping every one second
#keeps printing {data: "pong"} in terminal
$ while  true; do date; curl 127.0.0.1:3000/ping; sleep 1; echo \n; done;

#in Terminal 3
#does not block the event loop
$ date && curl 127.0.0.1:3000/event-loop-nonblocking-operation && date

In 2nd case, After calling the last command, Terminal 2 keeps printing {data: "pong"} and the operation defined in the /event-loop-blocking-operation's route handler keeps doing what it's doing.

How is it that calling await breathSpace(0) in the loop will run the operation in a non-blocking way?

As far as I know when the /event-loo-nonblocking-operation endpoint is called via curl:

  1. /event-loop-nonblocking-operation's corresponding route handler is enqueued to the I/O callback queue which is also known as poll queue
  2. route handler is dequeued from poll queue and pushed to call stack
  3. route handler's code is run line by line
    3.1 hash.update method is called (on each iteration of for loop)
    3.2 await breathSpace(0) is called (on each iteration of for loop)
    3.2.1 the (resolve, reject) => setTimeout(() => resolve(), 0) is enqueued to the Timer queue

Now considering event loop not getting blocked, Does this mean, that even though the first iteration of the loop is not done, event loop changes it's phase from poll to timer to handle the setTimeout which resolves the promise? How does it do the for loop without blocking?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文