简体   繁体   English

为什么while循环会阻塞事件循环?

[英]Why does a while loop block the event loop?

The following example is given in a Node.js book: Node.js 书中给出了以下示例:

var open = false;

setTimeout(function() {
  open = true
}, 1000)

while (!open) {
  console.log('wait');
}

console.log('open sesame');

Explaining why the while loop blocks execution, the author says:在解释为什么 while 循环会阻塞执行时,作者说:

Node will never execute the timeout callback because the event loop is stuck on this while loop started on line 7, never giving it a chance to process the timeout event! Node 永远不会执行超时回调,因为事件循环卡在第 7 行开始的这个 while 循环上,永远不会给它处理超时事件的机会!

However, the author doesn't explain why this happens in the context of the event loop or what is really going on under the hood.但是,作者没有解释为什么会在事件循环的上下文中发生这种情况,也没有解释引擎盖下到底发生了什么。

Can someone elaborate on this?有人可以详细说明一下吗? Why does node get stuck?为什么节点会卡住? And how would one change the above code, whilst retaining the while control structure so that the event loop is not blocked and the code will behave as one might reasonably expect;以及如何更改上述代码,同时保留while控制结构,以使事件循环不被阻塞并且代码将按照人们可能合理预期的方式运行; wait will be logged for only 1 second before the setTimeout fires and the process then exits after logging 'open sesame'.setTimeout触发之前,wait 将仅记录 1 秒,然后在记录“open sesame”后退出进程。

Generic explanations such as the answers to this question about IO and event loops and callbacks do not really help me rationalise this.通用解释,例如关于 IO 和事件循环和回调的这个问题的答案并不能真正帮助我合理化这一点。 I'm hoping an answer which directly references the above code will help.我希望直接引用上述代码的答案会有所帮助。

It's fairly simple really.这真的很简单。 Internally, node.js consists of this type of loop:在内部,node.js 由这种类型的循环组成:

  • Get something from the event queue从事件队列中获取一些东西
  • Run whatever task is indicated and run it until it returns运行指示的任何任务并运行它直到它返回
  • When the above task is done, get the next item from the event queue当上述任务完成后,从事件队列中获取下一项
  • Run whatever task is indicated and run it until it returns运行指示的任何任务并运行它直到它返回
  • Rinse, lather, repeat - over and over冲洗,起泡,重复 - 一遍又一遍

If at some point, there is nothing in the event queue, then go to sleep until something is placed in the event queue or until it's time for a timer to fire.如果在某个时刻,事件队列中没有任何内容,则进入睡眠状态,直到事件队列中放置了某些内容或直到计时器触发。


So, if a piece of Javascript is sitting in a while() loop, then that task is not finishing and per the above sequence, nothing new will be picked out of the event queue until that prior task is completely done.因此,如果一段 Javascript 处于while()循环中,则该任务尚未完成,并且按照上述顺序,在先前的任务完全完成之前,不会从事件队列中挑选出任何新内容。 So, a very long or forever running while() loop just gums up the works.因此,一个非常长或永远运行的while()循环只会使工作变得混乱。 Because Javascript only runs one task at a time (single threaded for JS execution), if that one task is spinning in a while loop, then nothing else can ever execute.因为 Javascript 一次只运行一个任务(单线程用于 JS 执行),如果该任务在 while 循环中旋转,那么其他任何事情都无法执行。

Here's a simple example that might help explain it:这是一个可能有助于解释它的简单示例:

 var done = false;

 // set a timer for 1 second from now to set done to true
 setTimeout(function() {
      done = true;
 }, 1000);

 // spin wait for the done value to change
 while (!done) { /* do nothing */}

 console.log("finally, the done value changed!");

Some might logically think that the while loop will spin until the timer fires and then the timer will change the value of done to true and then the while loop will finish and the console.log() at the end will execute.有些人可能从逻辑上认为 while 循环会一直旋转,直到计时器触发,然后计时器会将done的值更改为true ,然后 while 循环将完成,最后的console.log()将执行。 That is NOT what will happen.那不是会发生的事情。 This will actually be an infinite loop and the console.log() statement will never be executed.这实际上是一个无限循环,并且永远不会执行console.log()语句。

The issue is that once you go into the spin wait in the while() loop, NO other Javascript can execute.问题是,一旦您进入while()循环中的自旋等待,就无法执行其他 Javascript。 So, the timer that wants to change the value of the done variable cannot execute.因此,想要更改done变量值的计时器无法执行。 Thus, the while loop condition can never change and thus it is an infinite loop.因此,while 循环条件永远不会改变,因此它是一个无限循环。

Here's what happens internally inside the JS engine:以下是 JS 引擎内部发生的情况:

  1. done variable initialized to false done变量初始化为false
  2. setTimeout() schedules a timer event for 1 second from now setTimeout()从现在开始安排一个定时器事件 1 秒
  3. The while loop starts spinning while 循环开始旋转
  4. 1 second into the while loop spinning, the timer is ready to fire, but it won't be able to actually do anything until the interpreter gets back to the event loop在 while 循环旋转 1 秒后,计时器已准备好触发,但在解释器返回事件循环之前,它实际上无法执行任何操作
  5. The while loop keeps spinning because the done variable never changes. while 循环一直在旋转,因为done变量永远不会改变。 Because it continues to spin, the JS engine never finishes this thread of execution and never gets to pull the next item from the event queue or run the pending timer.因为它继续旋转,所以 JS 引擎永远不会完成这个执行线程,也永远不会从事件队列中拉下一个项目或运行挂起的计时器。

node.js is an event driven environment. node.js 是一个事件驱动的环境。 To solve this problem in a real world application, the done flag would get changed on some future event.为了在现实世界的应用程序中解决这个问题, done标志将在未来的某个事件中改变。 So, rather than a spinning while loop, you would register an event handler for some relevant event in the future and do your work there.因此,您将在未来为某些相关事件注册一个事件处理程序,而不是一个旋转的while循环,并在那里完成您的工作。 In the absolute worst case, you could set a recurring timer and "poll" to check the flag ever so often, but in nearly every single case, you can register an event handler for the actual event that will cause the done flag to change and do your work in that.在绝对最坏的情况下,您可以设置一个循环计时器和“轮询”来经常检查标志,但在几乎每一种情况下,您都可以为实际事件注册一个事件处理程序,这将导致done标志发生变化和做你的工作。 Properly designed code that knows other code wants to know when something has changed may even offer its own event listener and its own notification events that one can register an interest in or even just a simple callback.正确设计的代码知道其他代码想知道什么时候发生了变化,甚至可以提供自己的事件侦听器和自己的通知事件,人们可以注册感兴趣,甚至只是一个简单的回调。

This is a great question but I found a fix!这是一个很好的问题,但我找到了解决办法!

var sleep = require('system-sleep')
var done = false

setTimeout(function() {
  done = true
}, 1000)

while (!done) {
  sleep(100)
  console.log('sleeping')
}

console.log('finally, the done value changed!')

I think it works because system-sleep is not a spin wait.我认为它有效,因为system-sleep不是旋转等待。

There is another solution.还有另一种解决方案。 You can get access to event loop almost every cycle.几乎每个周期都可以访问事件循环。

let done = false;

setTimeout(() => {
  done = true
}, 5);

const eventLoopQueue = () => {
  return new Promise(resolve => 
    setImmediate(() => {
      console.log('event loop');
      resolve();
    })
  );
}

const run = async () => {
  while (!done) {
    console.log('loop');
    await eventLoopQueue();
  }
}

run().then(() => console.log('Done'));

Node is a single serial task.节点是一个单一的串行任务。 There is no parallelism, and its concurrency is IO bound.没有并行性,它的并发是 IO 绑定的。 Think of it like this: Everything is running on a single thread, when you make an IO call that is blocking/synchronous your process halts until the data is returned;可以这样想:一切都在单个线程上运行,当您进行阻塞/同步的 IO 调用时,您的进程会暂停,直到返回数据; however say we have a single thread that instead of waiting on IO(reading disk, grabbing a url, etc) your task continues on to the next task, and after that task is complete it checks that IO.但是,假设我们有一个线程,而不是等待 IO(读取磁盘、获取 url 等),您的任务会继续执行下一个任务,并在该任务完成后检查该 IO。 This is basically what node does, its an "event-loop" its polling IO for completion(or progress) on a loop.这基本上就是节点所做的,它是一个“事件循环”,它在循环上轮询 IO 以完成(或进度)。 So when a task does not complete(your loop) the event loop does not progress.因此,当任务未完成(您的循环)时,事件循环不会进行。 To put it simply.简而言之。

因为计时器需要返回并且正在等待循环完成添加到队列中,所以虽然超时在一个单独的线程中,并且可能确实完成了计时器,但是设置 done = true 的“任务”正在等待那个无限循环完成

var open = false;
const EventEmitter = require("events");
const eventEmitter = new EventEmitter();

setTimeout(function () {
  open = true;
  eventEmitter.emit("open_var_changed");
}, 1000);

let wait_interval = setInterval(() => {
console.log("waiting");
}, 100);

eventEmitter.on("open_var_changed", () => {
clearInterval(wait_interval);
console.log("open var changed to  ", open);
});

this exemple works and you can do setInterval and check if the open value changed inside it and it will work这个例子有效,您可以执行 setInterval 并检查其中的 open 值是否发生了变化,它会起作用

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM