淺談Node.js中的定時(shí)器
Node.js中定時(shí)器的實(shí)現(xiàn)
上一篇博文提到,在Node中timer并不是通過(guò)新開(kāi)線(xiàn)程來(lái)實(shí)現(xiàn)的,而是直接在event loop中完成。下面通過(guò)幾個(gè)JavaScript的定時(shí)器示例以及Node相關(guān)源碼來(lái)分析在Node中,timer功能到底是怎么實(shí)現(xiàn)的。
JavaScript中定時(shí)器功能的特點(diǎn)
無(wú)論是Node還是瀏覽器中,都有setTimeout和setInterval這兩個(gè)定時(shí)器函數(shù),并且其工作特點(diǎn)基本相同,因此下面僅以Node為例進(jìn)行分析。
我們知道,JavaScript中的定時(shí)器并不同于計(jì)算機(jī)底層的定時(shí)中斷。中斷到來(lái)時(shí),當(dāng)前執(zhí)行代碼會(huì)被打斷,轉(zhuǎn)去執(zhí)行定時(shí)中斷處理函數(shù)。而JavaScript的定時(shí)器到時(shí),如果當(dāng)前執(zhí)行線(xiàn)程沒(méi)有正在執(zhí)行的代碼,則執(zhí)行相應(yīng)的回調(diào)函數(shù);如果當(dāng)前有代碼在執(zhí)行中,JavaScript引擎既不會(huì)中斷當(dāng)前代碼轉(zhuǎn)去執(zhí)行回調(diào),也不會(huì)開(kāi)新的線(xiàn)程執(zhí)行回調(diào),而是當(dāng)前代碼執(zhí)行完畢之后才去處理。
console.time('A')
setTimeout(function () {
console.timeEnd('A');
}, 100);
var i = 0;
for (; i < 100000; i++) { }
執(zhí)行上面的代碼,可以看到最終輸出的時(shí)間并不是100ms左右,而是數(shù)秒。這說(shuō)明在循環(huán)完成之前,定時(shí)回調(diào)函數(shù)確實(shí)沒(méi)有被執(zhí)行,而是推遲到了循環(huán)結(jié)束。實(shí)際上在JavaScript代碼執(zhí)行中,所有的事件都無(wú)法得到處理,必須等到當(dāng)前代碼全部完成,才能去處理新的事件。這就是為什么在瀏覽器中運(yùn)行耗時(shí)JavaScript代碼時(shí),瀏覽器會(huì)失去響應(yīng)。為了應(yīng)對(duì)這種情況,我們可以采取Yielding Processes的技巧,將耗時(shí)的代碼分成小塊(chunks),每處理完一塊就執(zhí)行一次setTimeout,約定在一小段時(shí)間后才處理下一塊,而在這段空閑時(shí)間里,瀏覽器/Node可以去處理排隊(duì)中的事件。
補(bǔ)充資料
在JavaScript 高級(jí)程序設(shè)計(jì) 第三版第22章高級(jí)技巧中對(duì)高級(jí)定時(shí)器以及Yielding Processes有較詳細(xì)的討論。
Node中的timer實(shí)現(xiàn)
libuv對(duì)uv_loop_t類(lèi)型的初始化
上一篇博文提到Node會(huì)調(diào)用libuv的uv_run函數(shù)啟動(dòng)default_loop_ptr進(jìn)行事件調(diào)度,default_loop_ptr指向一個(gè)uv_loop_t類(lèi)型的變量default_loop_struct。Node啟動(dòng)時(shí)會(huì)調(diào)用uv_loop_init(&default_loop_struct)對(duì)其進(jìn)行初始化,uv_loop_init函數(shù)節(jié)選如下:
int uv_loop_init(uv_loop_t* loop) {
...
loop->time = 0;
uv_update_time(loop);
...
}
可以看到loop的time字段先被賦值為0,之后調(diào)用uv_update_time函數(shù),這會(huì)將最新的計(jì)數(shù)時(shí)間賦給loop.time。
初始化完成之后,default_loop_struct.time就有了一個(gè)初始值,與時(shí)間有關(guān)的操作都會(huì)與此值進(jìn)行比較從而確定是否調(diào)用相應(yīng)回調(diào)函數(shù)。
libuv的事件調(diào)度核心
前面提到uv_run函數(shù)就是libuv庫(kù)實(shí)現(xiàn)event loop的核心部分,下面是其流程圖:

這里簡(jiǎn)述一下上面與定時(shí)器相關(guān)的邏輯:
更新當(dāng)前l(fā)oop的time字段,這個(gè)字段標(biāo)志著當(dāng)前l(fā)oop概念下的“現(xiàn)在”;
檢查loop是否alive,也就是說(shuō)檢查loop中是否還有需要處理的任務(wù)(handlers/requests),如果沒(méi)有就不必循環(huán)了;
檢查注冊(cè)過(guò)的timer,如果某一個(gè)timer中指定的時(shí)間落后于當(dāng)前時(shí)間了,說(shuō)明該timer已到時(shí),于是執(zhí)行其對(duì)應(yīng)的回調(diào)函數(shù);
執(zhí)行一次I/O polling(即阻塞住線(xiàn)程,等待I/O事件發(fā)生),如果在下一個(gè)timer到期時(shí)還沒(méi)有任何I/O完成,則停止等待,執(zhí)行下一個(gè)timer的回調(diào)。
如果發(fā)生了I/O事件,則執(zhí)行對(duì)應(yīng)的回調(diào);由于執(zhí)行回調(diào)的時(shí)間里可能又有timer到期了,這里要再次檢查timer并執(zhí)行回調(diào)。
(實(shí)際上(4.)這里比較復(fù)雜,不僅僅是一步操作,這樣描述僅是為了不涉及其他細(xì)節(jié),而專(zhuān)注于timer的實(shí)現(xiàn)。)
Node會(huì)一直調(diào)用uv_run直到loop不再alive。
Node中的timer_wrap與timers
Node中有一個(gè)TimerWrap類(lèi),被注冊(cè)為Node內(nèi)部的timer_wrap模塊。
NODE_MODULE_CONTEXT_AWARE_BUILTIN(timer_wrap, node::TimerWrap::Initialize)
其中TimerWrap類(lèi)基本上就是對(duì)uv_timer_t的一個(gè)直接封裝,NODE_MODULE_CONTEXT_AWARE_BUILTIN是Node用于注冊(cè)built-in模塊的宏。
經(jīng)過(guò)這一步操作,JavaScript就可以拿到這個(gè)模塊進(jìn)行操作了。src/lib/timers.js文件使用JavaScript的形式把timer_wrap的功能封裝起來(lái),并導(dǎo)出了exports.setTimeout, exports.setInterval, exports.setImmediate等函數(shù)。
Node啟動(dòng)與global初始化
上一篇提到Node啟動(dòng)時(shí)會(huì)載入執(zhí)行環(huán)境LoadEnvironment(env),這個(gè)函數(shù)中非常重要的一步就是載入src/node.js并執(zhí)行,src/node.js會(huì)載入指定的模塊并初始化global和process。當(dāng)然,setTimeout等函數(shù)也會(huì)被src/node.js綁定到global對(duì)象上。
以上所述就是本文的全部?jī)?nèi)容了,希望大家能夠喜歡。
相關(guān)文章
Nodejs 和 Electron ubuntu下快速安裝過(guò)程
本文較為詳細(xì)的給大家介紹了Nodejs 和 Electron ubuntu下快速安裝過(guò)程,非常不錯(cuò),具有一定的參考借鑒價(jià)值,感興趣的朋友跟隨腳本之家小編一起學(xué)習(xí)吧2018-05-05
node.js報(bào)錯(cuò):Cannot find module ''ejs''的解決辦法
最近發(fā)現(xiàn)了node.js居然報(bào)錯(cuò)了,錯(cuò)誤提示為:Cannot find module 'ejs',后來(lái)找了找資料發(fā)現(xiàn)解決的方法其實(shí)很簡(jiǎn)單,下面通過(guò)這篇文章來(lái)一起看看吧,希望對(duì)同樣遇到這個(gè)問(wèn)題的朋友們能有所幫助。2016-12-12
使用?Node.js和Express搭建服務(wù)器的過(guò)程步驟詳解
Node.js?是一個(gè)開(kāi)源、跨平臺(tái)的?JavaScript?運(yùn)行時(shí)環(huán)境,這篇文章主要介紹了如何使用?Node.js和Express搭建服務(wù)器,需要的朋友可以參考下2023-09-09
Node.js Windows Binary二進(jìn)制文件安裝方法
這篇文章主要介紹了Node.js Windows Binary二進(jìn)制文件安裝,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-05-05
基于promise.js實(shí)現(xiàn)nodejs的promises庫(kù)
promise是JavaScript實(shí)現(xiàn)優(yōu)雅編程的一個(gè)非常不錯(cuò)的輕量級(jí)框架。該框架可以讓你從雜亂的多重異步回調(diào)代碼中解脫出來(lái),并把精力集中到你的業(yè)務(wù)邏輯上。2014-07-07
node.js基于fs模塊對(duì)系統(tǒng)文件及目錄進(jìn)行讀寫(xiě)操作的方法詳解
這篇文章主要介紹了node.js基于fs模塊對(duì)系統(tǒng)文件及目錄進(jìn)行讀寫(xiě)操作的方法,結(jié)合實(shí)例形式分析了nodejs使用fs模塊針對(duì)文件與目錄的讀寫(xiě)、創(chuàng)建、刪除等相關(guān)操作技巧,需要的朋友可以參考下2017-11-11

