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