浅谈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 相关文章推荐
jquery实现文本框鼠标右击无效以及不能输入的代码
Nov 05 Javascript
IE6/7/8/9不支持exec的简写方式
May 25 Javascript
window.onresize 多次触发的解决方法
Nov 08 Javascript
ANGULARJS中用NG-BIND指令实现单向绑定的例子
Dec 08 Javascript
jQuery EasyUI封装简化操作
Sep 18 Javascript
从零学习node.js之文件操作(三)
Feb 21 Javascript
angular实现spa单页面应用实例
Jul 10 Javascript
浅谈JavaScript的innerWidth与innerHeight
Oct 12 Javascript
详谈vue+webpack解决css引用图片打包后找不到资源文件的问题
Mar 06 Javascript
js使用Promise实现简单的Ajax缓存
Nov 14 Javascript
Angular+ionic实现折叠展开效果的示例代码
Jul 29 Javascript
vue.js watch经常失效的场景与解决方案
Jan 07 Vue.js
浅析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 simple_html_dom.php+正则 采集文章代码
2009/12/24 PHP
解析:php调用MsSQL存储过程使用内置RETVAL获取过程中的return值
2013/07/03 PHP
PHP中ob_start函数的使用说明
2013/11/11 PHP
ThinkPHP之import方法实例详解
2014/06/20 PHP
php中引用&amp;的用法分析【变量引用,函数引用,对象引用】
2016/12/12 PHP
PHP CURL使用详解
2019/03/21 PHP
PHP如何解决微信文章图片防盗链
2020/12/09 PHP
jquery星级插件、支持页面中多次使用
2012/03/25 Javascript
早该知道的7个JavaScript技巧
2013/03/27 Javascript
JavaScript基础语法、dom操作树及document对象
2014/12/02 Javascript
jquery操作复选框checkbox的方法汇总
2015/02/05 Javascript
jQuery实现美观的多级动画效果菜单代码
2015/09/06 Javascript
基于JavaScript如何实现ajax调用后台定义的方法
2015/12/29 Javascript
JS字符串的切分用法实例
2016/02/22 Javascript
微信小程序 form组件详解
2016/10/25 Javascript
AngularJS使用ng-repeat和ng-if实现数据的删选显示效果示例【适用于表单数据的显示】
2016/12/13 Javascript
JSON中key动态设置及JSON.parse和JSON.stringify()的区别
2016/12/29 Javascript
微信小程序  TLS 版本必须大于等于1.2问题解决
2017/02/22 Javascript
如何正确理解javascript的模块化
2017/03/02 Javascript
浅谈angular.copy() 深拷贝
2017/09/14 Javascript
layui获取选中行数据的实例讲解
2018/08/19 Javascript
Vue.set() this.$set()引发的视图更新思考及注意事项
2018/08/30 Javascript
vue路由中前进后退的一些事儿
2019/05/18 Javascript
微信小程序webview组件交互,内联h5页面并网页实现微信支付实现解析
2019/08/16 Javascript
[01:38]完美世界DOTA2联赛(PWL)宣传片:第一站
2020/10/26 DOTA
[47:18]完美世界DOTA2联赛循环赛 IO vs FTD BO2第一场 11.05
2020/11/06 DOTA
python制作小说爬虫实录
2017/08/14 Python
在python下读取并展示raw格式的图片实例
2019/01/24 Python
微信浏览器取消缓存的方法
2015/03/28 HTML / CSS
Spanx塑身衣官网:美国知名内衣品牌
2017/01/11 全球购物
香港演唱会订票网站:StubHub香港
2019/10/10 全球购物
DTD的含义以及作用
2014/01/26 面试题
应聘护理专业毕业自荐书范文
2014/02/12 职场文书
班级道德讲堂实施方案
2014/02/24 职场文书
电台编导求职信
2014/05/06 职场文书
react中props 的使用及进行限制的方法
2021/04/28 Javascript