zzrshuiwuhen 2019-06-20
上一篇博文提到,在Node中timer并不是通过新开线程来实现的,而是直接在event loop中完成。下面通过几个JavaScript的定时器示例以及Node相关源码来分析在Node中,timer功能到底是怎么实现的。
无论是Node还是浏览器中,都有setTimeout和setInterval这两个定时器函数,并且其工作特点基本相同,因此下面仅以Node为例进行分析。
我们知道,JavaScript中的定时器并不同于计算机底层的定时中断。中断到来时,当前执行代码会被打断,转去执行定时中断处理函数。而JavaScript的定时器到时,如果当前执行线程没有正在执行的代码,则执行相应的回调函数;如果当前有代码在执行中,JavaScript引擎既不会中断当前代码转去执行回调,也不会开新的线程执行回调,而是当前代码执行完毕之后才去处理。
console.time('A') setTimeout(function () { console.timeEnd('A'); }, 100); var i = 0; for (; i < 100000; i++) { }
执行上面的代码,可以看到最终输出的时间并不是100ms左右,而是数秒。这说明在循环完成之前,定时回调函数确实没有被执行,而是推迟到了循环结束。实际上在JavaScript代码执行中,所有的事件都无法得到处理,必须等到当前代码全部完成,才能去处理新的事件。这就是为什么在浏览器中运行耗时JavaScript代码时,浏览器会失去响应。为了应对这种情况,我们可以采取Yielding Processes的技巧,将耗时的代码分成小块(chunks),每处理完一块就执行一次setTimeout
,约定在一小段时间后才处理下一块,而在这段空闲时间里,浏览器/Node可以去处理排队中的事件。
在JavaScript 高级程序设计 第三版
第22章高级技巧
中对高级定时器以及Yielding Processes有较详细的讨论。
上一篇博文提到Node会调用libuv的uv_run
函数启动default_loop_ptr
进行事件调度,default_loop_ptr
指向一个uv_loop_t
类型的变量default_loop_struct
。Node启动时会调用uv_loop_init(&default_loop_struct)
对其进行初始化,uv_loop_init
函数节选如下:
int uv_loop_init(uv_loop_t* loop) { ... loop->time = 0; uv_update_time(loop); ... }
可以看到loop
的time
字段先被赋值为0,之后调用uv_update_time
函数,这会将最新的计数时间赋给loop.time
。
初始化完成之后,default_loop_struct.time
就有了一个初始值,与时间有关的操作都会与此值进行比较从而确定是否调用相应回调函数。
前面提到uv_run
函数就是libuv库实现event loop的核心部分,下面是其流程图:
这里简述一下上面与定时器相关的逻辑:
loop
的time
字段,这个字段标志着当前loop
概念下的“现在”;loop
是否alive,也就是说检查loop
中是否还有需要处理的任务(handlers/requests
),如果没有就不必循环了;Node会一直调用uv_run
直到loop
不再alive。
Node中有一个TimerWrap
类,被注册为Node内部的timer_wrap
模块。
NODE_MODULE_CONTEXT_AWARE_BUILTIN(timer_wrap, node::TimerWrap::Initialize)
其中TimerWrap
类基本上就是对uv_timer_t
的一个直接封装,NODE_MODULE_CONTEXT_AWARE_BUILTIN
是Node用于注册built-in模块的宏。
经过这一步操作,JavaScript就可以拿到这个模块进行操作了。src/lib/timers.js
文件使用JavaScript的形式把timer_wrap
的功能封装起来,并导出了exports.setTimeout, exports.setInterval, exports.setImmediate
等函数。
上一篇提到Node启动时会载入执行环境LoadEnvironment(env)
,这个函数中非常重要的一步就是载入src/node.js
并执行,src/node.js
会载入指定的模块并初始化global
和process
。当然,setTimeout
等函数也会被src/node.js
绑定到global
对象上。
至此,setTimeout/setInterval
这类定时器函数已经可以为JavaScript所用了。