浅谈Node.js中的定时器


Posted in Javascript onJune 18, 2015

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的核心部分,下面是其流程图:

浅谈Node.js中的定时器

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

更新当前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对象上。

以上所述就是本文的全部内容了,希望大家能够喜欢。

Javascript 相关文章推荐
js 图片轮播(5张图片)
Dec 30 Javascript
基于jQuery的仿flash的广告轮播
Nov 05 Javascript
JS下载文件|无刷新下载文件示例代码
Apr 17 Javascript
原生JavaScript+LESS实现瀑布流
Dec 12 Javascript
Angular外部使用js调用Angular控制器中的函数方法或变量用法示例
Aug 05 Javascript
Mvc提交表单的四种方法全程详解
Aug 10 Javascript
jquery validation验证表单插件
Jan 07 Javascript
AngularJS 使用ng-repeat报错 [ngRepeat:dupes]
Jan 19 Javascript
jquery实现弹窗功能(窗口居中显示)
Feb 27 Javascript
jQuery插件zTree实现单独选中根节点中第一个节点示例
Mar 08 Javascript
详解JavaScript中return的用法
May 08 Javascript
JS实现网页抢购功能(触发,终止脚本)
Nov 27 Javascript
浅析AngularJS中的生命周期和延迟处理
Jun 18 #Javascript
Node.js事件驱动
Jun 18 #Javascript
详解AngularJS的通信机制
Jun 18 #Javascript
javascript背景时钟实现方法
Jun 18 #Javascript
移动Web中图片自适应的两种JavaScript解决方法
Jun 18 #Javascript
javascript随机显示背景图片的方法
Jun 18 #Javascript
利用JavaScript的AngularJS库制作电子名片的方法
Jun 18 #Javascript
You might like
PHP去除数组中重复的元素并按键名排序函数
2008/08/18 PHP
PHP APC配置文件2套和参数详解
2014/06/11 PHP
PHP pthreads v3下同步处理synchronized用法示例
2020/02/21 PHP
yii 框架实现按天,月,年,自定义时间段统计数据的方法分析
2020/04/04 PHP
js 禁用浏览器的后退功能的简单方法
2008/12/10 Javascript
基于jQuery捕获超链接事件进行局部刷新代码
2012/05/10 Javascript
jquery分页插件AmSetPager(自写)
2013/04/15 Javascript
JS实现侧悬浮浮动实例代码
2013/11/29 Javascript
JavaScript三元运算符的多种使用技巧
2015/04/16 Javascript
第四篇Bootstrap网格系统偏移列和嵌套列
2016/06/21 Javascript
bootstrap滚动监控器使用方法解析
2017/01/13 Javascript
angular+ionic 的app上拉加载更新数据实现方法
2017/01/16 Javascript
JS实现简单的天数计算器完整实例
2017/04/28 Javascript
Vue 2.0在IE11中打开项目页面空白的问题解决
2017/07/16 Javascript
vue-cli之router基本使用方法详解
2017/10/17 Javascript
vue中使用refs定位dom出现undefined的解决方法
2017/12/21 Javascript
解决vue-router在同一个路由下切换,取不到变化的路由参数问题
2018/09/01 Javascript
Vue实现本地购物车功能
2018/12/05 Javascript
在JavaScript中如何访问暂未存在的嵌套对象
2019/06/18 Javascript
vue3.0实现插件封装
2020/12/14 Vue.js
[44:40]KG vs LGD 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/16 DOTA
深入理解Python中变量赋值的问题
2017/01/12 Python
Python使用pymysql从MySQL数据库中读出数据的方法
2018/07/25 Python
Python 调用 Outlook 发送邮件过程解析
2019/08/08 Python
Python中函数的返回值示例浅析
2019/08/28 Python
Python实现Restful API的例子
2019/08/31 Python
将python字符串转化成长表达式的函数eval实例
2020/05/11 Python
Tensorflow tf.tile()的用法实例分析
2020/05/22 Python
Python+PyQt5实现灭霸响指功能
2020/05/25 Python
详解用Python调用百度地图正/逆地理编码API
2020/07/02 Python
python3 使用ssh隧道连接mysql的操作
2020/12/05 Python
python中numpy.empty()函数实例讲解
2021/02/05 Python
简单总结CSS3中视窗单位Viewport的常见用法
2016/02/04 HTML / CSS
《草原的早晨》教学反思
2014/04/08 职场文书
财务出纳岗位职责
2015/03/31 职场文书
运动会5000米加油稿
2015/07/21 职场文书