为什么while循环会阻塞节点事件循环?
以下示例在Node.js book中给出:
var open = false; setTimeout(function() { open = true }, 1000) while (!open) { console.log('wait'); } console.log('open sesame');
作者解释了为什么while循环阻止执行,他说:
节点将永远不会执行超时callback,因为事件循环在第7行开始的while循环中停留,从不给它一个机会来处理超时事件!
然而,作者并没有解释为什么会发生在事件循环的背景下,或者真正发生了什么。
有人能详细说明吗? 为什么节点卡住了? 而如何改变上面的代码,同时保留while
控制结构,这样事件循环就不会被阻塞,并且代码将按照人们可以合理预期的方式工作; 等待将在setTimeout
触发前仅logging1秒,然后在logging“打开芝麻”之后,进程退出。
通用的解释,如关于IO和事件循环和callback这个问题的答案,并不真的帮助我理顺这一点。 我希望直接引用上面的代码的答案将有所帮助。
这真的很简单。 在内部,node.js由这种types的循环组成:
- 从事件队列中获取一些东西
- 运行指定的任何任务并运行它直到返回
- 完成上述任务后,从事件队列中获取下一个项目
- 运行指定的任何任务并运行它直到返回
- 冲洗,起泡,重复 – 一遍又一遍
如果在某个时刻,事件队列中没有任何事情,那么就进入hibernate状态,直到事件队列中有事件发生。
所以,如果一段Javascript坐在一个while()
循环中,那么这个任务并没有完成,并且按照上面的顺序,在事件队列中没有新的东西被挑选出来,直到前一个任务完成。 所以,一个很长或永远运行的while()
循环只会使作品变干。 因为Javascript一次只运行一个任务(单线程执行JS),如果这个任务在while循环中旋转,那么其他任何事情都不能执行。
下面是一个简单的例子,可以帮助解释它:
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!");
有些人可能逻辑上认为,while循环会旋转,直到定时器触发,然后定时器将done
值更改为true
,然后while循环完成,最后的console.log()
将执行。 那不会发生什么事情。 这实际上是一个无限循环,并且console.log()
语句永远不会被执行。
问题是,一旦你在while()
循环中进入旋转等待,没有其他的Javascript可以执行。 所以,想要改变done
variables值的计时器不能执行。 因此,while循环条件永远不会改变,因此它是一个无限循环。
以下是JS引擎内部发生的情况:
-
done
variables初始化为false
-
setTimeout()
从现在开始计划1秒的计时器事件 - while循环开始旋转
- 1秒进入while循环,定时器在JS引擎内部触发,定时器callback被添加到事件队列中。 这可能发生在JS引擎内部的另一个线程上。
- while循环继续旋转,因为
done
variables不会改变。 因为它继续旋转,所以JS引擎永远不会完成这个执行线程,也永远不会从事件队列中取出下一个项目。
节点是一个单一的串行任务。 没有并行性,并发性是IO绑定的。 可以这样想:当一个IO调用阻塞/同步你的进程,直到返回数据时,所有东西都在一个线程上运行, 然而,如果我们有一个单独的线程,而不是等待IO(读取磁盘,抓取一个url等等),那么你的任务继续到下一个任务,在这个任务完成之后,它会检查这个IO。 这基本上是节点所做的事情,它是一个“事件循环”轮询IO,用于在循环上完成(或进度)。 所以当一个任务没有完成(你的循环)事件循环不会进行。 简而言之。
这是一个很好的问题,但我find了一个解决scheme!
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!')
我认为这是有效的,因为system-sleep
不是一个等待。