fab*_*mlk 2 javascript event-loop node.js
Node.js文档说:
定时器的执行顺序将根据调用它们的上下文而有所不同。如果两者都从主模块中调用,则计时将受进程性能的限制(这可能会受到机器上运行的其他应用程序的影响)。
例如,如果我们运行以下不在 I/O 周期(即主模块)内的脚本,则两个计时器的执行顺序是不确定的,因为它受进程性能的约束:
Run Code Online (Sandbox Code Playgroud)// 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 文档所说:
setimmediate-vs-settimeout 定时器的执行顺序将根据调用它们的上下文而有所不同。如果两者都从主模块中调用,则计时将受进程性能的限制(这可能会受到机器上运行的其他应用程序的影响)。例如,如果我们运行以下不在 I/O 周期(即主模块)内的脚本,则两个计时器的执行顺序是不确定的,因为它受进程性能的约束:
为什么 ?。
node.js 中的每个事件都是由uv_run()libuv的函数驱动的。部分代码
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);
............
Run Code Online (Sandbox Code Playgroud)
所以正如node.js Doc所解释的,我们可以在代码中匹配事件循环的每个阶段。
定时器相位 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);
但是如果我们在循环进入计时器阶段之前仔细查看代码,它会调用
uv__update_time(loop); 初始化循环时间。
void uv_update_time(uv_loop_t* loop) {
uv__update_time(loop);
}
Run Code Online (Sandbox Code Playgroud)
发生的事情是uv__update_time(loop)调用一个函数uv__hrtime
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;
}
Run Code Online (Sandbox Code Playgroud)
这个调用uv__hrtime依赖于平台并且是 CPU 耗时的工作,因为它对clock_gettime进行系统调用。它受机器上运行的其他应用程序的影响。
#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);
}
Run Code Online (Sandbox Code Playgroud)
一旦返回,在事件循环中调用 Timer 阶段。
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);
}
}
Run Code Online (Sandbox Code Playgroud)
如果当前循环时间大于超时时间,则运行 Timer 阶段的回调。需要注意的另一件重要事情是setTimeout设置为0在内部转换为1. 同样作为hr_time以纳秒为单位的返回时间,如图所示的这种行为timeout_vs_immediate.js现在变得更具解释性。
如果第一个循环之前的准备工作超过1ms了TimerPhase 调用与其关联的回调。如果它小于1msEvent-loop 继续下一个阶段并setImmediate在循环的检查阶段和循环setTimeout的下一个滴答中运行回调。
但愿能阐明周围的不确定性的行为方式setTimeout和setImmediate当两者都从主模块中调用。