NodeJs是一個(gè)平臺(tái),構(gòu)建在v8上(js語言解釋器),采用事件驅(qū)動(dòng)、
總所周知,NodeJs是用javascript語言開發(fā)的,javascript是一個(gè)單線程語言。當(dāng)Node服務(wù)器收到成千上萬計(jì)的并發(fā)請(qǐng)求的時(shí)候,卻不會(huì)造成阻塞。原因就是nodeJs的事件驅(qū)動(dòng)。
- 每個(gè)NodeJs進(jìn)程只有一個(gè)主線程在執(zhí)行程序代碼,形成一個(gè)執(zhí)行棧。
- 主線程之外,還維護(hù)了一個(gè)事件隊(duì)列。當(dāng)用戶的網(wǎng)絡(luò)請(qǐng)求或者其他的異步操作到來的時(shí)候,node都會(huì)把它放在Event Queue中,此時(shí)并不會(huì)立即執(zhí)行它,代碼也不會(huì)被阻塞,繼續(xù)走下去,直到主線程代碼執(zhí)行完畢。
- 主線程的代碼執(zhí)行完畢之后,通過Event Loop,也就是事件循環(huán)機(jī)制,開始到Event Queue的開頭取出第一個(gè)事件,從線程池中分配一個(gè)線程去執(zhí)行這個(gè)事件,接下來取出第二個(gè)事件,再從線程池中分配一個(gè)線程去執(zhí)行,然后第三個(gè)、第四個(gè)。主線程不斷檢查事件隊(duì)列中是否有未執(zhí)行的事件,直到事件隊(duì)列中所有事件都執(zhí)行完畢了。此后每當(dāng)有新的事件加入到事件隊(duì)列中,都會(huì)通知主線程按順序去取出交給Event Loop處理。當(dāng)所有的事件執(zhí)行完畢之后,會(huì)通知主線程,主線程執(zhí)行回調(diào),線程歸還線程池。
- 主線程不斷重復(fù)以上三步。
總結(jié)
我們看到的NodeJs單線程只是一個(gè)js主線程,本質(zhì)上的異步操作還是由線程池完成的,node將所有的阻塞操作都交給了內(nèi)部的線程池去實(shí)現(xiàn),本身只負(fù)責(zé)不斷的往返調(diào)度,并沒有進(jìn)行真正的I/O操作,從而實(shí)現(xiàn)異步非阻塞I/O,這便是node單線程的事件驅(qū)動(dòng)的精髓了。
NodeJs中的事件循環(huán)的實(shí)現(xiàn)
NodeJs采用V8作為js的引擎,而I/O處理使用了自己設(shè)計(jì)的libuv,libuv是一個(gè)基于事件驅(qū)動(dòng)的跨平臺(tái)抽象層,封裝了不同操作系統(tǒng)一些底層特性,對(duì)外提供統(tǒng)一的API,事件循環(huán)機(jī)制也是它里面的實(shí)現(xiàn)。
Event Loop的執(zhí)行順序
根據(jù)NodeJs的官方介紹,每次事件循環(huán)都包含了6個(gè)階段:
- timers階段:這個(gè)階段執(zhí)行timer(setTimeout、setInterval)的回調(diào)
- I/O callbacks階段:執(zhí)行一些系統(tǒng)調(diào)用錯(cuò)誤,比如網(wǎng)絡(luò)通信的錯(cuò)誤回調(diào)
- idle,prepare階段,僅node內(nèi)部調(diào)用
- poll階段:獲取新的I/O事件,適當(dāng)?shù)臈l件下node將阻塞在這里
- check階段:執(zhí)行setImmediate()的回調(diào)
- close callbacks階段:執(zhí)行socket的close事件回調(diào)
setImmediate和setTimeout執(zhí)行順序的隨機(jī)性
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
})
在瀏覽器中的setImmediate優(yōu)先。因?yàn)闉g覽器中setTimeout有時(shí)間誤差,即使setTimeout(fn, 0),實(shí)際上相當(dāng)于setTimeout(fn, 4);
在node中的執(zhí)行結(jié)果確是隨機(jī)的。

node中的事件循環(huán)階段
NodeJs中事件循環(huán)模型與瀏覽器相比大致相同,但是node中事件循環(huán)是分階段的。

每個(gè)階段都有一個(gè)先進(jìn)先出的對(duì)調(diào)隊(duì)列要執(zhí)行。而每個(gè)階段都有自己的特殊之處。簡(jiǎn)單的說,就是當(dāng)事件循環(huán)進(jìn)入到某個(gè)階段之后,會(huì)執(zhí)行該階段特定的任意操作,然后才會(huì)執(zhí)行這個(gè)階段里面的回調(diào)。當(dāng)隊(duì)列被執(zhí)行完,或者執(zhí)行的回調(diào)達(dá)到上限之后,事件循環(huán)才會(huì)到下一個(gè)階段。
timers
一個(gè)timer指定一個(gè)下限時(shí)間而不是準(zhǔn)確時(shí)間,在達(dá)到這個(gè)下線時(shí)間后執(zhí)行回調(diào)。在指定時(shí)間過后,timers會(huì)盡早執(zhí)行回調(diào),但是系統(tǒng)調(diào)度或者其他的回調(diào)的執(zhí)行會(huì)延遲它。
從技術(shù)上講,poll階段控制timers什么時(shí)候執(zhí)行,而執(zhí)行的具體位置在timers。
I/O callbacks
這個(gè)階段執(zhí)行一些系統(tǒng)操作的回調(diào),比如說TCP連接錯(cuò)誤
idle,prepare
系統(tǒng)內(nèi)部的一些調(diào)用
poll
這是最復(fù)雜的階段
poll階段有兩個(gè)功能:一個(gè)是執(zhí)行下限時(shí)間已經(jīng)達(dá)到timers的回調(diào),一是處理poll隊(duì)列里面的事件
注:Node很多Api都是基于事件訂閱完成的,這些api的回調(diào)應(yīng)該都在poll階段完成。
check階段
執(zhí)行setImmediate的回調(diào)
close callback階段
執(zhí)行socket的close事件回調(diào)