上一则昌明提及,在 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 对 uvloopt 类型的调用
Node 会调用 libuv 的 uvrun 表达式开启 defaultloopptr 展开该事件运维,defaultloopptr 对准一个uvloopt类别的表达式defaultloopstruct。Node开启时能调用uvloopinit(&defaultloopstruct)对其展开调用,uvloopinit表达式节选如下:
int uvloopinit(uvloopt* loop){ … loop->time =0; uvupdatetime(loop);…}
能看见loop的time字段先被赋值为0,之后调用uvupdatetime表达式,这会将最捷伊计数天数赋给loop.time。
调用顺利完成之后,defaultloopstruct.time就有了一个初始值,与天数有关的操作都会与此值展开比较从而确定是否调用适当反弹表达式。
libuv 的该事件运维核心
前面提及uvrun表达式就是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会一直调用uvrun直到loop不再alive。
Node 中的 timerwrap 与 timers
Node中有一个TimerWrap类,被注册为Node内部的timerwrap模块。
NODEMODULECONTEXTAWAREBUILTIN(timerwrap, node::TimerWrap::Initialize)
其中TimerWrap类基本上就是对 uvtimert 的一个直接封装,NODEMODULECONTEXTAWAREBUILTIN 是Node用于注册built-in模块的宏。
经过这一步操作,JavaScript就能拿到这个模块展开操作了。src/lib/timers.js 文件使用JavaScript的形式把timerwrap的机能封装起来,并导出了 exports.setTimeout, exports.setInterval, exports.setImmediate 等表达式。
Node 开启与 global 调用
上一则提及 Node 开启时能载入继续执行环境 LoadEnvironment(env),这个表达式中非常重要的一步就是载入 src/node.js并继续执行,src/node.js会载入指定的模块并调用global和process。当然,setTimeout 等表达式也会被 src/node.js 绑定到 global 对象上。
至此,setTimeout/setInterval这类计时器表达式已经能为JavaScript所用了。