这里有新鲜出炉的 Node.js 教程,程序狗速度看过来!
Node.js 是一个基于 Chrome JavaScript 运行时建立的一个平台, 用来方便地搭建快速的 易于扩展的网络应用 · Node.js 借助事件驱动, 非阻塞 I/O 模型变得轻量和高效, 非常适合 运行在分布式设备 的 数据密集型 的实时应用
本文给大家分享的是 Node.js 中的定时器的相关资料,十分的全面细致,有需要的小伙伴可以参考下。
Node.js 中定时器的实现
上一篇博文提到,在 Node 中 timer 并不是通过新开线程来实现的,而是直接在 event loop 中完成。下面通过几个 JavaScript 的定时器示例以及 Node 相关源码来分析在 Node 中,timer 功能到底是怎么实现的。
JavaScript 中定时器功能的特点
无论是 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 中的 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 函数节选如下:
- 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 就有了一个初始值,与时间有关的操作都会与此值进行比较从而确定是否调用相应回调函数。
libuv 的事件调度核心
前面提到 uv_run 函数就是 libuv 库实现 event loop 的核心部分,下面是其流程图:
这里简述一下上面与定时器相关的逻辑:
更新当前 loop 的 time 字段,这个字段标志着当前 loop 概念下的 "现在";
检查 loop 是否 alive,也就是说检查 loop 中是否还有需要处理的任务(handlers/requests),如果没有就不必循环了; 检查注册过的 timer,如果某一个 timer 中指定的时间落后于当前时间了,说明该 timer 已到时,于是执行其对应的回调函数; 执行一次 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 会载入指定的模块并初始化 global 和 process。当然,setTimeout 等函数也会被 src/node.js 绑定到 global 对象上。
来源: http://www.phperz.com/article/17/0426/270785.html