Javascript 为什么nodejs事件循环在第一次运行后是不确定的?
Node.js说: 计时器的执行顺序将根据调用它们的上下文而有所不同。如果两者都是从主模块中调用的,则计时将受到进程性能的限制(这可能会受到机器上运行的其他应用程序的影响) 例如,如果我们运行不在I/O周期内的以下脚本(即主模块),则两个计时器的执行顺序是不确定的,因为它受进程性能的约束:Javascript 为什么nodejs事件循环在第一次运行后是不确定的?,javascript,node.js,event-loop,Javascript,Node.js,Event Loop,Node.js说: 计时器的执行顺序将根据调用它们的上下文而有所不同。如果两者都是从主模块中调用的,则计时将受到进程性能的限制(这可能会受到机器上运行的其他应用程序的影响) 例如,如果我们运行不在I/O周期内的以下脚本(即主模块),则两个计时器的执行顺序是不确定的,因为它受进程性能的约束: // timeout_vs_immediate.js setTimeout(function timeout () { console.log('timeout'); },0); setImmediate
// timeout_vs_immediate.js
setTimeout(function timeout () {
console.log('timeout');
},0);
setImmediate(function immediate () {
console.log('immediate');
});
$ node timeout_vs_immediate.js
timeout
immediate
$ node timeout_vs_immediate.js
immediate
timeout
在同一页中,我想我理解了事件循环是如何工作的,但是在这次主运行之后,为什么偶数循环不能正确地完成它的工作呢?与I/O周期相比,有什么不同呢?正如Node.js doc所说:
计时器的执行顺序将根据调用它们的上下文而有所不同。如果两者都是从主模块中调用的,则计时将受到进程性能的限制(这可能会受到机器上运行的其他应用程序的影响)。
例如,如果我们运行不在I/O周期内的以下脚本(即主模块),则两个计时器的执行顺序是不确定的,因为它受进程性能的约束:
// timeout_vs_immediate.js
setTimeout(function timeout () {
console.log('timeout');
},0);
setImmediate(function immediate () {
console.log('immediate');
});
$ node timeout_vs_immediate.js
timeout
immediate
$ node timeout_vs_immediate.js
immediate
timeout
为什么
node.js中的每个事件都由libuv的uv\u run()
函数驱动。部分代码
int uv_run(uv_loop_t* loop, uv_run_mode mode) {
int 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);
uv__run_timers(loop);
ran_pending = uv__run_pending(loop);
uv__run_idle(loop);
uv__run_prepare(loop);
......
uv__io_poll(loop, timeout);
uv__run_check(loop);
uv__run_closing_handles(loop);
............
正如node.js文档所解释的,我们可以匹配代码中事件循环的每个阶段
定时器相位uv运行定时器(循环)代码>
I/O回调运行挂起=uv运行挂起(循环)代码>
空闲/准备uv\u运行\u空闲(循环);uv\u运行\u准备(循环)代码>
轮询uv\uuu io\u轮询(循环,超时)代码>
检查紫外线运行检查(循环)代码>
关闭回调uv\u运行\u关闭\u句柄(循环)代码>
多相
但如果我们在循环进入计时器阶段之前仔细查看代码,它会调用
uv\u更新时间(循环)代码>以初始化循环时间
void uv_update_time(uv_loop_t* loop) {
uv__update_time(loop);
}
所发生的是uv\uu更新\u时间(循环)
调用函数uv\uu时间
UV_UNUSED(static void uv__update_time(uv_loop_t* loop)) {
/* Use a fast time source if available. We only need millisecond precision.
*/
loop->time = uv__hrtime(UV_CLOCK_FAST) / 1000000;
}
对uv\uuu hr时间的调用取决于平台,并且是cpu耗时的工作,因为它使系统
打电话给我。它受到计算机上运行的其他应用程序的影响
#define NANOSEC ((uint64_t) 1e9)
uint64_t uv__hrtime(uv_clocktype_t type) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (((uint64_t) ts.tv_sec) * NANOSEC + ts.tv_nsec);
}
一旦返回,将在事件循环中调用计时器阶段
void uv__run_timers(uv_loop_t* loop) {
...
for (;;) {
....
handle = container_of(heap_node, uv_timer_t, heap_node);
if (handle->timeout > loop->time)
break;
....
uv_timer_again(handle);
handle->timer_cb(handle);
}
}
如果循环的当前时间大于超时,则运行计时器阶段的回调。
另一个需要注意的重要事项是setTimeout
当设置为0
时,会在内部转换为1
。
同样由于hr\u time
以纳秒为单位的返回时间,这种行为如timeout\u vs\u immediate.js
所示,现在变得更加容易解释
如果第一个循环之前的准备工作花费了超过1ms
的时间,则计时器
阶段调用与其相关联的回调。如果小于1ms
事件循环将继续到下一阶段,并在循环的检查阶段运行setImmediate
回调,在
循环的下一个滴答声
希望这能澄清setTimeout
和setImmediate
在主模块内调用时的非确定性行为