初窥JavaScript事件机制的实现(二)—— Node.js中定时器的实现

上一篇博文提到,在Node中timer并非经过新开线程来实现的,而是直接在event loop中完成。下面经过几个JavaScript的定时器示例以及Node相关源码来分析在Node中,timer功能究竟是怎么实现的。node

JavaScript中定时器功能的特色

不管是Node仍是浏览器中,都有setTimeout和setInterval这两个定时器函数,而且其工做特色基本相同,所以下面仅以Node为例进行分析。segmentfault

咱们知道,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有较详细的讨论。oop

Node中的timer实现

libuv对uv_loop_t类型的初始化

上一篇博文提到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函数节选以下:ui

int uv_loop_init(uv_loop_t* loop) {
  ...
  loop->time = 0;
  uv_update_time(loop);
  ...
}

能够看到looptime字段先被赋值为0,以后调用uv_update_time函数,这会将最新的计数时间赋给loop.timespa

初始化完成以后,default_loop_struct.time就有了一个初始值,与时间有关的操做都会与此值进行比较从而肯定是否调用相应回调函数。线程

libuv的事件调度核心

前面提到uv_run函数就是libuv库实现event loop的核心部分,下面是其流程图:设计

uv_run

这里简述一下上面与定时器相关的逻辑:code

  1. 更新当前looptime字段,这个字段标志着当前loop概念下的“如今”;
  2. 检查loop是否alive,也就是说检查loop中是否还有须要处理的任务(handlers/requests),若是没有就没必要循环了;
  3. 检查注册过的timer,若是某一个timer中指定的时间落后于当前时间了,说明该timer已到时,因而执行其对应的回调函数;
  4. 执行一次I/O polling(即阻塞住线程,等待I/O事件发生),若是在下一个timer到期时尚未任何I/O完成,则中止等待,执行下一个timer的回调。
    若是发生了I/O事件,则执行对应的回调;因为执行回调的时间里可能又有timer到期了,这里要再次检查timer并执行回调。
    (实际上(4.)这里比较复杂,不只仅是一步操做,这样描述仅是为了避免涉及其余细节,而专一于timer的实现。)

Node会一直调用uv_run直到loop再也不alive。

Node中的timer_wrap与timers

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启动与global初始化

上一篇提到Node启动时会载入执行环境LoadEnvironment(env),这个函数中很是重要的一步就是载入src/node.js并执行,src/node.js会载入指定的模块并初始化globalprocess。固然,setTimeout等函数也会被src/node.js绑定到global对象上。

至此,setTimeout/setInterval这类定时器函数已经能够为JavaScript所用了。

相关文章
相关标签/搜索