什么是事件循環(huán)?本篇文章給大家介紹一下Node中的事件循環(huán),希望對大家有所幫助!
什么是事件循環(huán)?
盡管JavaScript是單線程的,但是事件循環(huán)盡可能的使用系統(tǒng)內(nèi)核允許Node.js執(zhí)行非阻塞I/O操作 盡管大部分現(xiàn)代內(nèi)核是多線程的,他們可以在后臺處理多線程任務(wù)。當(dāng)一個任務(wù)完成時,內(nèi)核告訴Node.js,然后適當(dāng)?shù)幕卣{(diào)會被加入到循環(huán)中執(zhí)行,這篇文章會進一步詳細的介紹這個話題
時間循環(huán)解釋
當(dāng)Node.js開始執(zhí)行時,首先會初始化事件循環(huán),處理提供的輸入腳本(或者放入REPL,本文檔未涉及)這會執(zhí)行異步 API調(diào)用,調(diào)度計時器,或調(diào)用 process.nextTick(),然后開始處理事件循環(huán)
下圖展示了事件循環(huán)執(zhí)行順序的簡化概覽
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
每一個盒子代表著事件循環(huán)的一個階段
每一個階段有一個FIFO的隊列 callback 執(zhí)行,然而每一個階段基于它自己的方式執(zhí)行,總體來講,當(dāng)事件循環(huán)進入到一個階段里,它將執(zhí)行當(dāng)前階段的任何操作,開始執(zhí)行當(dāng)前階段隊列中的回調(diào)直到隊列完全消耗完或者執(zhí)行到隊列的最大數(shù)據(jù)。當(dāng)隊列消耗完或者達到最大數(shù)量,事件循環(huán)就會移動到下一個階段。
階段概述
- timers 這個階段執(zhí)行 setTimeout() 和 setInterval() 的回調(diào)
- pending callbacks 執(zhí)行 I/O 回調(diào)推遲到下一個循環(huán)迭代
- idle,prepare 僅在內(nèi)部使用
- poll 檢索新的 I/O 事件;執(zhí)行 I/O 相關(guān)的回調(diào)(幾乎所有相關(guān)的回調(diào),關(guān)閉回調(diào),)
- check setImmediate() 會在此階段調(diào)用
- close callbacks 關(guān)閉回調(diào),例如: socket.on('close', …)
在事件循環(huán)的每個過程中,Node.js檢查是否它正在等待異步的I/O和計時器,如果沒有則完全關(guān)閉
階段詳情
timer
一個計時器指定一個回調(diào)會被執(zhí)行的臨界點,而不是人們想讓它執(zhí)行的時間,計時器會在指定的過去時間之后盡可能早的執(zhí)行,然而,操作系統(tǒng)調(diào)度或者其他回調(diào)會讓它延遲執(zhí)行。
從技術(shù)角度上講,poll 階段決定了回調(diào)何時執(zhí)行
例如,你設(shè)置了一個計時器,100 ms之后執(zhí)行,然而你的腳本異步讀取了一個文件花費了 95ms
const fs = require('fs'); function someAsyncOperation(callback) { // Assume this takes 95ms to complete fs.readFile('/path/to/file', callback); } const timeoutScheduled = Date.now(); setTimeout(() => { const delay = Date.now() - timeoutScheduled; console.log(`${delay}ms have passed since I was scheduled`); }, 100); // do someAsyncOperation which takes 95 ms to complete someAsyncOperation(() => { const startCallback = Date.now(); // do something that will take 10ms... while (Date.now() - startCallback < 10) { // do nothing } });
當(dāng)事件循環(huán)進入了 poll 階段,是一個空的隊列,(fs.readFile() 還沒有完成),因此它會等待剩余的毫秒數(shù)直到最快的計時器閾值到達,當(dāng)95 ms之后,fs.readFile() 完成了讀文件并且會花費10 ms完成添加到poll 階段并且執(zhí)行完畢,當(dāng)回調(diào)完成,隊列中沒有回調(diào)要執(zhí)行了,事件循環(huán)循環(huán)返回到timers 階段,執(zhí)行計時器的回調(diào)。在這個例子中,你會看到計時器被延遲了105 ms之后執(zhí)行
為了防止 poll 階段阻塞事件循環(huán),libuv(實現(xiàn)了事件循環(huán)和平臺上所有的異步行為的C語言庫)在 poll 階段同樣也有一個最大值停止輪訓(xùn)