nodejs 异步之 Timer &Tick; 篇

摘要:
Nodejs异步计时器&Tick;文章-Nodenodejs异步计时器&Tick;文章定时器:在前端开发中,我们经常使用setTimeout函数组。这组函数不属于语言标准,它们只是扩展。在浏览器中,它们属于BOM表,也就是说,它们的确切定义是:window。setTimeout和窗口。警报、窗口打开和其他功能处于同一级别。部分:从上面的分析中,我们可以得出至少两个简单的结论:#1如果可能,在调用setTimeout时,尝试使用相同的超时值#2尝试使用进程Next Tick代替setTimeout标签:原始文章windyrobin于2011年9月13日1:45发布。Windyrobin2012年1月55日重新编辑并分享给weibo2,回复#1snoopy。最终总结产生了很多好处,

nodejs 异步之 Timer &Tick; 篇 - CNode

nodejs 异步之 Timer &Tick; 篇

Timer:

在前端开发中,我们 经常会使用setTimeout 函数组,这组函数其实不属于语言标准,他们只是extentsion ,在浏览器中,他们属于 BOM(浏览器对象扩展),即它的确切定义为:window.setTimeout ,和window.alert , window.open 等函数处于同一层次。

你可以在浏览器的控制台监测window 对象 ,或查看chromium 的实现 :
http://codesearch.google.com/#OAMlx_jo-ck/src/third_party/WebKit/Source/WebCore/page/DOMWindow.h
在nodejs ,当然也要实现自己的Timer ,在nodejs 的src 目录下,timer.h/cc 是对 libev 中的 ev_timer 进行了一层浅包装,在src/node.js 文件中,把这组函数放置于全局范围中(相当于浏览器中window)
startup.globalTimeouts = function() {

  global.setTimeout = function() {

    var t = NativeModule.require('timers');

    return t.setTimeout.apply(this, arguments);

  };

  global.setInterval = function() {

    var t = NativeModule.require('timers');

    return t.setInterval.apply(this, arguments);

  };

...

通过我们对libev的分析,我们已经知道,在libev中队timer的 添加/删除/更新 操作都是 O(lg(n)) ,
(libev 分析 http://cnodejs.org/blog/?p=2489
但其实在我们的日常开发中,以网络套接字为例,我们往往会对其设置timeout,即如果一段时间(120s 或 60s)此套接字没有活动事件发生,我们就关闭它(这种技巧非常重要,否则由于一些内部或外部的原因,很容易出现描述符占用过多的现象),注意到,我们的超时时间是一个统一的值,这时候,我们可以采用如下算法:(lib/_linklist.js 真实实现)
nodejs 异步之 Timer &Tick; 篇第1张
当我们在不同的时刻多次 调用 setTimeout(fn ,1000) 时,我们把所有的这些事件仅由一个timer 来管理, 这些事件被放到一个双向链表中,其顺序自然而然的就是按时间来排序的,当某一时刻,timer 触发时,会依次的从prev链表头中取出节点,进行检查, 如果超时则触发...
(参见lib/timer_legacy.js 由于libev无法在windows上 很好的工作,所以nodejs项目组又对libev,libeio 作了一层封装 ,称为libuv, 使用了传统的方法文件以_legacy 结尾,使用了libuv 的以_uv 结尾,如net_legacy.js ,net_uv.js ,timers_legacy.js ,timers_uv.js 等,)
当我们需要更改某异步事件时,比如,我们添加超时控制的socket上发生了read/ write 事件,这时候,我们需要重新reset 计时,我们仅需要把这个事件从链表中转移到链表尾部即可
(参见 lib/net_legacy.js)
删除时的情况与此类似,所以在此场景下,仅耗费一个ev_timer ,而我们的所有异步事件的添加/删除/更新 都是 O(1) 复杂度.
注意,在lib/timer_*.js 中,当超时值<= 0 时是不做优化的,

exports.exports.setTimeout = function(callback, after) {

  if (after <= 0) {

    // Use the slow case for after == 0

   timer = new Timer();

   timer.ontimeout = callback;

  else{

    ...

  }

同时setInterval 函数也是没做优化的.
nextTick :
nextTick是个很有意思的东西,它其实是一个prepare watcher 来实现的,在libev中的event loop 的每次迭代,在nodejs 中就叫做 “Tick” ,而在libev 中,支持prepare watcher ,它在每次迭代开始时触发 ,在src/node.cc 中 我们看到类似代码(v4.9为例):

ev_prepare_init(&node::prepare_tick_watcher, node::PrepareTick);

ev_prepare_start(EV_DEFAULT_UC_ &node::prepare_tick_watcher);

ev_unref(EV_DEFAULT_UC);

在函数 PrepareTick 中 会调用一个回调函数,此函数 _tickCallback 在 src/node.js 中:

startup.processNextTick =function() {

  var nextTickQueue = [];

  process._tickCallback = function() {

  ...

  }

  process.nextTick = function(callback) {

    nextTickQueue.push(callback);

    process._needTickCallback();

   };

};

当我们调用API process.nextTick 时,其实就是向nextTickQueue 这个闭包队列中添加一个函数,当下次事件循环开始时,会自动触发prepare_tick_watcher ,调用我们设定的函数。
注意,上面有个问题,那就是如果没有机会进行下次事件循环,比如,此时没有实质性的watcher可供监测了,这时事件循环就会退出 ,为了避免着这种情况, nextTick 函数中会调用一次 process._needTickCallback() ,这个函数会使tick_spinner (一个idle watcher)处于活跃状态 , 来防止事件循环的退出。
由nextTick 原理我们可知,当我们添加一个待处理事件时,其复杂度为O(1) ,但如果你用setTimeout(fn ,0) 的话,如我们在libev分析中所讲,其一进一出均为 O(lg(n)) , 所以nodejs官方文档讲 :

On the next loop around the event loop call this callback. This is not a simple alias to setTimeout(fn, 0), it's much more efficient.

nextTick 确实高效的多,诚不我欺也!
小节:
通过以上分析 ,我们至少得出2条粗浅的结论:
#1 如果可能的话,调用setTimeout时,尽量使用相同的超时值
#2 尽量用process.nextTick 来代替 setTimeout(fn ,0)

标签:


原创文章


windyrobin 在 2011-9-13 21:45发布


windyrobin 在 2012-1-19 11:55重新编辑


分享到 weibo

2 回复
nodejs 异步之 Timer &amp;Tick; 篇第2张

#1
snoopy

最后小结收益良多啊,希望爱多兄能有更多深入浅出的文章~
继续关注中~


snoopy 在 2011-9-14 22:15回复

nodejs 异步之 Timer &amp;Tick; 篇第3张

#2
suqian

timer复用。。。解除疑惑。。。以后得多深入源码。。


suqian 在 2011-9-23 00:21回复

免责声明:文章转载自《nodejs 异步之 Timer &amp;amp;Tick; 篇》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇webdriver与JS操作浏览器元素IR-drop问题的分析与修复(五):Padding Clock Cells: ICC2 &amp;amp; Innovus下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

k8s之node主机修改IP

  k8s的node主机修改IP以后配置   原k8s node主机IP为172.16.20.182需要修改成172.16.20.183   查看原node    node主机修改IP以后,修改node的kubele配置文件 # cat /opt/kubernetes/cfg/kubelet # cat /opt/kubernetes/cfg/kubel...

NodeJs实现下载Excel文件

nodejs作为一门新的语言,报表功能也不是十分完善。 (1).js-xlsx: 目前 Github 上 star 数量最多的处理 Excel 的库,支持解析多种格式表格XLSX / XLSM / XLSB / XLS / CSV,解析采用纯js实现,写入需要依赖nodejs或者FileSaver.js实现生成写入Excel,可以生成子表Excel,功能强...

Nodejs mysql 数据库增、删、改、查 操作

Nodejs mysql的增、删、改、查操作 Nodejs连接mysql的增、删、改、查操作(转载 自:http://blog.sina.com.cn/s/blog_5a6efa330102vctw.html) 一、准备 nodejs的教程,大多以操作mongodb为示例。但是mongodb有一些局限性,具体官网上有说。我打算用MySQL,因为多少还有...

阿里云CentOS搭建EasyMock​

阿里云CentOS搭建EasyMock​ 简介 Easy Mock 是一个可视化,并且能快速生成 模拟数据 的持久化服务。 特性 支持接口代理 支持快捷键操作 支持协同编辑 支持团队项目 支持 Restful 支持 Swagger 1.2 & 2.0 基于 Swagger 快速创建项目 支持显示接口入参与返回值 支持显示实体类 支持灵活性与扩...

EF Core 原理从源码出发(二)

紧接着我的上一篇博客,可以点击这里回到上一篇博客,上回分析到ef 两个重要的对象,StateManager和ChangeTracker这个对象,当我们向DbContext添加对象的时候我们会调用如下代码。 1 private EntityEntry<TEntity> SetEntityState<TEntity>(...

在Linux系统配置Nodejs环境的最简单步骤,部署多个thinkjs(nodejs)项目

发现一台服务器部署管理多个nodejs服务,可以采用二级域名weekly.mwcxs.top,也可以采用固定后缀www.mwcxs.top/weekly的方式,本文先从固定后缀的方式部署管理多个nodejs服务。 以下详细的介绍,以周报企业管理系统为例进行部署,欢迎fork和start,源码地址:https://github.com/saucxs/week...