nodejs為什么可以處理高并發(fā)而不阻塞?

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)。

  1. 每個(gè)NodeJs進(jìn)程只有一個(gè)主線程在執(zhí)行程序代碼,形成一個(gè)執(zhí)行棧。
  2. 主線程之外,還維護(hù)了一個(gè)事件隊(duì)列。當(dāng)用戶的網(wǎng)絡(luò)請(qǐng)求或者其他的異步操作到來的時(shí)候,node都會(huì)把它放在Event Queue中,此時(shí)并不會(huì)立即執(zhí)行它,代碼也不會(huì)被阻塞,繼續(xù)走下去,直到主線程代碼執(zhí)行完畢。
  3. 主線程的代碼執(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),線程歸還線程池。
  4. 主線程不斷重復(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)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容