node mysql 事件循环_NodeJs 的 Event loop 事件循环机制详解
什么是事件輪詢
事件循環(huán)是 Node.js 處理非阻塞 I/O 操作的機制——盡管 JavaScript 是單線程處理的——當有可能的時候,它們會把操作轉(zhuǎn)移到系統(tǒng)內(nèi)核中去。
下面的圖表顯示了事件循環(huán)的概述以及操作順序。
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ IO / callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
三大關(guān)鍵階段
timer:執(zhí)行定時器時,如 setTimeout、setInterval,在 timers 階段執(zhí)行
poll:異步操作,比如文件I/O,網(wǎng)絡(luò)I/O等,通過'data'、 'connect'等事件通知 JS 主線程并執(zhí)行回調(diào)的,此階段就是 poll 輪詢階段
check:這是一個比較簡單的階段,直接執(zhí)行 setImmdiate 的回調(diào)。
注意,若 2 階段結(jié)束后,當前存在到時間的定時器,那么拿出來執(zhí)行,eventLoop 將再回到 timer 階段
階段流程概述
timers: 本階段執(zhí)行已經(jīng)安排的 setTimeout() 和 setInterval() 的回調(diào)函數(shù)
IO / callbacks: 執(zhí)行 I/O 異常的回調(diào),如TCP 連接遇到 ECONNREFUSED
idle, prepare: 僅系統(tǒng)內(nèi)部使用,只是表達空閑、預(yù)備狀態(tài)(第2階段結(jié)束,poll 未觸發(fā)之前)
poll: 檢索新的 I/O 事件;執(zhí)行與 I/O 相關(guān)的回調(diào)(幾乎所有情況下,除了關(guān)閉的回調(diào)函數(shù)),node 將在此處阻塞。
check: setImmediate() 回調(diào)函數(shù)在這里執(zhí)行.
close callbacks: 一些準備關(guān)閉的回調(diào)函數(shù),如:socket.on('close', ...)
在每次運行的事件循環(huán)之間,Node.js 檢查它是否在等待任何異步 I/O 或計時器,如果沒有的話,則關(guān)閉干凈。
timers
timers 指定 可執(zhí)行所提供回調(diào) 的 時間閾值,poll 階段 控制何時定時器執(zhí)行。
一旦 poll queue 為空,事件循環(huán)將檢查 已達到時間閾值的timer計時器。如果一個或多個計時器已準備就緒,則事件循環(huán)將回到 timer 階段以執(zhí)行這些計時器的回調(diào)
pending callbacks
此階段對某些系統(tǒng)操作(如 TCP 錯誤類型)執(zhí)行回調(diào)。例如,如果 TCP 套接字在嘗試連接時接收到 ECONNREFUSED,則某些 *nix 的系統(tǒng)希望等待報告錯誤。這將被排隊以在 pending callbacks 階段執(zhí)行。
poll
輪詢 階段有兩個重要的功能:
計算應(yīng)該阻塞和 poll I/O 的時間。
然后,處理 poll 隊列里的事件。
當事件循環(huán)進入 poll階段且 timers scheduled,將發(fā)生以下兩種情況之一:
if the poll queue is not empty, 事件循環(huán)將循環(huán)訪問其回調(diào)隊列并同步執(zhí)行它們,直到隊列已用盡,或者達到了與系統(tǒng)相關(guān)的硬限制
If the poll queue is empty,還有兩件事發(fā)生
如果腳本已按 setImmediate() 排定,則事件循環(huán)將結(jié)束 輪詢 階段,并繼續(xù) 檢查 階段以執(zhí)行這些計劃腳本。
如果腳本尚未按 setImmediate()排定,則事件循環(huán)將等待回調(diào)添加到隊列中,然后立即執(zhí)行。
一旦 poll queue 為空,事件循環(huán)將檢查 已達到時間閾值的timer計時器。如果一個或多個計時器已準備就緒,則事件循環(huán)將回到 timer 階段以執(zhí)行這些計時器的回調(diào)。
check
通常,在執(zhí)行代碼時,事件循環(huán)最終會命中輪詢階段,等待傳入連接、請求等。但是,如果回調(diào)已計劃為 setImmediate(),并且輪詢階段變?yōu)榭臻e狀態(tài),則它將結(jié)束并繼續(xù)到檢查階段而不是等待輪詢事件。
setImmediate() 實際上是一個在事件循環(huán)的單獨階段運行的特殊計時器。它使用一個 libuv API 來安排回調(diào)在 poll 階段完成后執(zhí)行。
close callbacks
如果套接字或處理函數(shù)突然關(guān)閉(例如 socket.destroy()),則'close' 事件將在這個階段發(fā)出。否則它將通過 process.nextTick() 發(fā)出。
setImmediate() 對比 setTimeout()
setImmediate() 和 setTimeout() 很類似,但何時調(diào)用行為完全不同。
setImmediate() 設(shè)計為在當前 輪詢 階段完成后執(zhí)行腳本。
setTimeout() 計劃在毫秒的最小閾值經(jīng)過后運行的腳本。
執(zhí)行計時器的順序?qū)⒏鶕?jù)調(diào)用它們的上下文而異,如果二者都從主模塊內(nèi)調(diào)用,則計時將受進程性能的約束,兩個計時器的順序是非確定性的。
// timeout_vs_immediate.js
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
$ node timeout_vs_immediate.js
timeout
immediate
$ node timeout_vs_immediate.js
immediate
timeout
但是,如果你把這兩個函數(shù)放入一個 I/O 循環(huán)內(nèi)調(diào)用,setImmediate 總是被優(yōu)先調(diào)用:
// timeout_vs_immediate.js
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
});
$ node timeout_vs_immediate.js
immediate
timeout
$ node timeout_vs_immediate.js
immediate
timeout
使用 setImmediate() 超過 setTimeout() 的主要優(yōu)點是 setImmediate() 在任何計時器(如果在 I/O 周期內(nèi))都將始終執(zhí)行,而不依賴于存在多少個計時器。
process.nextTick()
process.nextTick() 在技術(shù)上不是事件循環(huán)的一部分,無論事件循環(huán)的當前階段如何,都將在當前操作完成后處理 nextTickQueue。這里的一個操作被視作為一個從 C++ 底層處理開始過渡,并且處理需要執(zhí)行的 JavaScript 代碼。
回顧我們的關(guān)系圖,任何時候在給定的階段中調(diào)用 process.nextTick(),所有傳遞到 process.nextTick() 的回調(diào)將在事件循環(huán)繼續(xù)之前得到解決。這可能會造成一些糟糕的情況, 因為它允許您通過進行遞歸 process.nextTick() 來“餓死”您的 I/O 調(diào)用,阻止事件循環(huán)到達 輪詢 階段。
一個題目
// test.js
process.nextTick(function() {
console.log('next tick');
});
setTimeout(function() {
console.log('settimeout');
});
(async function() {
console.log('async promise');
})();
setImmediate(function() {
console.log('setimmediate');
});
$ node test.js
async promise
next tick
settimeout
setimmediate
沒有await,async那句其實是同步執(zhí)行的,故而第一句輸出。
next tick 在任何事件循環(huán)階段繼續(xù)之前得到解決,故而第二句
setTimeout 在主線程中與 setImmediate 的執(zhí)行順序是非確定性的
// test.js
setTimeout(function () {
process.nextTick(function() {
console.log('next tick');
});
setTimeout(function() {
console.log('settimeout');
});
(async function() {
console.log('async promise');
})();
setImmediate(function() {
console.log('setimmediate');
});
})
$ node test.js
async promise
next tick
setimmediate
settimeout
setimmediate 與 settimeout 放入一個 I/O 循環(huán)內(nèi)調(diào)用,則 setImmediate 總是被優(yōu)先調(diào)用
node >= 11 ?
setTimeout(()=>{
console.log('timer1')
setImmediate(function () { console.log('immd 1'); })
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
setImmediate(function () { console.log('immd 2'); })
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
在 node 11 及以上版本打印得
timer1
promise1
timer2
promise2
immd 1
immd 2
在 node 版本為 8.11.2 打印
timer1
timer2
promise1
promise2
immd 1
immd 2
這是因為 < 11 得版本中
若第一個定時器任務(wù)出隊并執(zhí)行完,發(fā)現(xiàn)隊首的任務(wù)仍然是一個定時器,那么就將微任務(wù)暫時保存,直接去執(zhí)行新的定時器任務(wù),當新的定時器任務(wù)執(zhí)行完后,再一一執(zhí)行中途產(chǎn)生的微任務(wù)。
nodejs 和 瀏覽器關(guān)于eventLoop的主要區(qū)別
兩者最主要的區(qū)別在于瀏覽器中的微任務(wù)是在每個相應(yīng)的宏任務(wù)中執(zhí)行的,而nodejs中的微任務(wù)是在不同階段之間執(zhí)行的。
總結(jié)
以上是生活随笔為你收集整理的node mysql 事件循环_NodeJs 的 Event loop 事件循环机制详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql server 5.0的jdb
- 下一篇: npoi 执行公式_生成excel文件时