?譯者 | 盧鑫旺
當(dāng)我們啟動(dòng)一個(gè)Node.js應(yīng)用程序時(shí),它會(huì)加載事件循環(huán)并將必要的回調(diào)函數(shù)添加到回調(diào)棧中。在本文中,你將詳細(xì)了解Node.js中的事件循環(huán)是如何工作的。
一、為什么你應(yīng)該了解Node.js中的事件循環(huán)
以下幾點(diǎn)闡述為什么了解事件循環(huán)很重要:
理解事件循環(huán)的原理有助于你寫出更高效的代碼
當(dāng)你的應(yīng)用程序出現(xiàn)異常錯(cuò)誤時(shí)能幫你更容易地調(diào)試你的代碼
二、什么是事件循環(huán)
根據(jù)Node.js官方網(wǎng)站的說法,事件循環(huán)允許Node.js執(zhí)行非阻塞I/O操作——盡管JavaScript是單線程的——會(huì)盡可能地將操作轉(zhuǎn)移到系統(tǒng)內(nèi)核中。
我們可以把這個(gè)定義分解為三個(gè)關(guān)鍵字:
- 非阻塞I/O操作
- 單線程
- 系統(tǒng)內(nèi)核
1.非阻塞I/O操作
如果一個(gè)操作的執(zhí)行沒有被阻塞,我們就說這個(gè)程序是非阻塞的。既然我們?cè)谶@里提到了非阻塞,那么我們也應(yīng)該提到什么是阻塞。它只是意味著你必須在一個(gè)操作完成后才能再完成另一個(gè)操作。
2.單線程
如果一個(gè)程序只有一個(gè)調(diào)用棧,并且它使用了先進(jìn)先出的概念,在同一個(gè)時(shí)刻只能執(zhí)行一個(gè)任務(wù),那么這個(gè)程序就是單線程的。這意味著棧上的第一個(gè)程序總是在下一個(gè)程序之前運(yùn)行。雖然JavaScript看起來是單線程的語言,不過這只取決于它運(yùn)行的環(huán)境。
3.系統(tǒng)內(nèi)核
在這里,內(nèi)核只是指運(yùn)行程序的操作系統(tǒng)。Javascript是單線程的,但Node.js在執(zhí)行多個(gè)輸入輸出(I/O)操作時(shí)能夠不阻塞線程。它通過盡可能將此操作交給操作系統(tǒng)(例如Linux、Windows、Mac OS X等)來實(shí)現(xiàn)這一點(diǎn)。操作大多被轉(zhuǎn)移到操作系統(tǒng)中;這就是Javascript與Node.js的區(qū)別。
三、Node.js中的事件循環(huán)是如何工作的
當(dāng)我們啟動(dòng)node應(yīng)用程序時(shí),事件循環(huán)立即開始運(yùn)行。事件循環(huán)有多個(gè)階段,每個(gè)階段都有要執(zhí)行的回調(diào)隊(duì)列。當(dāng)事件循環(huán)被添加到特定階段時(shí),它將在該特定階段執(zhí)行一些操作,然后在該階段隊(duì)列中執(zhí)行一些回調(diào)。
這將一直持續(xù)到隊(duì)列為空或已經(jīng)執(zhí)行了最大數(shù)量的回調(diào)函數(shù)。當(dāng)達(dá)到限制時(shí),事件循環(huán)會(huì)進(jìn)入下一階段執(zhí)行相同的操作。
有四個(gè)最重要的階段:
- 到期時(shí)間回調(diào)
- I/O輪詢和回調(diào)
- setImmediate回調(diào)
- close回調(diào)
1.到期時(shí)間回調(diào)
該階段負(fù)責(zé)處理過期定時(shí)器的回調(diào)函數(shù)。
舉例:
是一個(gè)函數(shù),它設(shè)置了一個(gè)在一定時(shí)間后過期的定時(shí)器。
因此,如果這個(gè)定時(shí)器有回調(diào)函數(shù)的話,那么它們將是事件循環(huán)首先處理的函數(shù)。
如果計(jì)時(shí)器稍后到期,在處理其他某個(gè)階段的時(shí)間內(nèi),則只有當(dāng)事件循環(huán)返回到第一階段時(shí),才會(huì)調(diào)用該計(jì)時(shí)器的回調(diào)。它在所有四個(gè)階段都是這樣工作的。
2.I/O輪詢和回調(diào)
輪詢基本上意味著搜索準(zhǔn)備好處理的新I/O事件,并將其放入回調(diào)隊(duì)列。理解在Node應(yīng)用程序的上下文中,I/O只是指網(wǎng)絡(luò)和文件訪問之類的事情,這一點(diǎn)至關(guān)重要。
舉例:
在這個(gè)階段,99%的代碼都會(huì)被執(zhí)行,因?yàn)樵诘湫偷腘ode應(yīng)用程序中,我們需要做的大部分工作都與網(wǎng)絡(luò)和讀取文件有關(guān)。
3.setImmediate回調(diào)
如果我們想在輪詢和執(zhí)行階段的I/O之后立即處理回調(diào),我們使用這個(gè)特殊的計(jì)時(shí)器。這在一些更高級(jí)的情況下可能是重要的。
4.close回調(diào)
在這個(gè)階段,所有的close關(guān)閉事件都會(huì)被處理,例如,當(dāng)一個(gè)Web服務(wù)器關(guān)閉時(shí)。這就完成了事件循環(huán)的第四個(gè)階段。
注意:Node.js事件循環(huán)內(nèi)部還使用了其他事件,但就本文而言,以上四個(gè)事件對(duì)我們來說是至關(guān)重要的。
如上所述,我們完成了這個(gè)過程,這只是事件循環(huán)中的一個(gè)周期。循環(huán)結(jié)束后,Node.js決定是繼續(xù)循環(huán)還是退出循環(huán)。
Node只是通過檢查是否有任務(wù)(例如定時(shí)器或I/0任務(wù))仍然在后臺(tái)運(yùn)行來做到這一點(diǎn)。如果沒有,它就會(huì)退出應(yīng)用程序。如果有待處理的任務(wù),它們會(huì)繼續(xù)處理下一個(gè)任務(wù),例如處理HTTP請(qǐng)求或讀取文件。
這基本上就是Node事件循環(huán)的全部內(nèi)容。
四、避免阻塞事件循環(huán)
因?yàn)镹ode.js中的所有內(nèi)容最終都運(yùn)行在單個(gè)線程中,你可以讓數(shù)百萬用戶同時(shí)訪問同一個(gè)池,這使得Nde.js非常輕量級(jí)和可擴(kuò)展。但與此同時(shí),它有阻塞單個(gè)線程的危險(xiǎn),這將使整個(gè)應(yīng)用程序變慢,甚至停止應(yīng)用程序。
五、避免阻塞事件循環(huán)的一些準(zhǔn)則
作為開發(fā)人員,你有責(zé)任避免事件循環(huán)的阻塞。下面的這些準(zhǔn)則可以幫助你避免阻塞事件循環(huán)。
- 不要在回調(diào)函數(shù)中使用fs、crypto和Zlib模塊中函數(shù)的同步版本。
- 處理較大的對(duì)象時(shí),使用JSON時(shí)要小心。
- 不要使用過于復(fù)雜的正則表達(dá)式(例如,嵌套的量詞)。
- 不要在嵌套對(duì)象上執(zhí)行復(fù)雜的計(jì)算。
六、關(guān)于Node.js事件循環(huán)的最后思考
事件循環(huán)使Node.js中的異步編程成為可能。這使得它成為Node設(shè)計(jì)中最重要的特性。這使得Node.js與其他平臺(tái)完全不同。
它負(fù)責(zé)處理所有傳入的事件,并通過將較重的任務(wù)轉(zhuǎn)到線程池并自己執(zhí)行最簡單的工作來執(zhí)行編排。
原文鏈接:https://hackernoon.com/how-do-event-loops-in-nodejs-work
譯者介紹
盧鑫旺,51CTO社區(qū)編輯,編程語言愛好者,對(duì)數(shù)據(jù)庫,架構(gòu),云原生有濃厚興趣。?
























