摘要:下面通過幾個的定時器示例以及相關源碼來分析在中,功能到底是怎么實現的。我們知道,中的定時器并不同于計算機底層的定時中斷。補充資料在高級程序設計第三版第章高級技巧中對高級定時器以及有較詳細的討論。至此,這類定時器函數已經可以為所用了。
上一篇博文提到,在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的核心部分,下面是其流程圖:
這里簡述一下上面與定時器相關的邏輯:
更新當前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與timersNode中有一個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對象上。
至此,setTimeout/setInterval這類定時器函數已經可以為JavaScript所用了。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/85764.html
摘要:如果當前沒有事件也沒有定時器事件,則返回。相關資料關于的架構及設計思路的事件討論了使用線程池異步運行代碼。下一篇初窺事件機制的實現二中定時器的實現 在瀏覽器中,事件作為一個極為重要的機制,給予JavaScript響應用戶操作與DOM變化的能力;在Node.js中,事件驅動模型則是其高并發能力的基礎。 學習JavaScript也需要了解它的運行平臺,為了更好的理解JavaScript的事...
摘要:簡介項目命名為就是一個服務器單純開發一個服務器的想法,變成構建網絡應用的一個基本框架發展為一個強制不共享任何資源的單線程,單進程系統。單線程弱點無法利用多核錯誤會引起整個應用退出,應用的健壯性大量計算占用導致無法繼續調用異步。 NodeJs簡介 Ryan Dahl項目命名為:web.js 就是一個Web服務器.單純開發一個Web服務器的想法,變成構建網絡應用的一個基本框架.Node發展...
摘要:事件循環事件循環是實現異步的一種方法,也是的執行機制。最后的最后是一門單線程語言是的執行機制部分內容轉自 1.單線程 javascript是一門單線程語言 2.javascript事件循環 同步任務 異步任務 showImg(https://segmentfault.com/img/bVbufUd?w=1268&h=1062);除了廣義的同步任務和異步任務,我們對任務有更精細的定義...
摘要:事件觸發線程主要負責將準備好的事件交給引擎線程執行。它將不同的任務分配給不同的線程,形成一個事件循環,以異步的方式將任務的執行結果返回給引擎。 Fundebug經作者浪里行舟授權首發,未經同意請勿轉載。 前言 本文我們將會介紹 JS 實現異步的原理,并且了解了在瀏覽器和 Node 中 Event Loop 其實是不相同的。 一、線程與進程 1. 概念 我們經常說 JS 是單線程執行的,...
閱讀 3401·2021-10-08 10:15
閱讀 5441·2021-09-23 11:56
閱讀 1467·2019-08-30 15:55
閱讀 445·2019-08-29 16:05
閱讀 2725·2019-08-29 12:34
閱讀 2036·2019-08-29 12:18
閱讀 914·2019-08-26 12:02
閱讀 1650·2019-08-26 12:00