給初學者的一篇文章:Node.js Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

我是家碧發表於2018-04-08

1.Node能夠解決什麼問題?

Node的首要目標是提供一種簡單的,用於建立高效能伺服器的開發工具
Web伺服器的瓶頸在於併發的使用者量(在瀏覽器端經常會接受到一些客戶端的請求,完了轉發給我們的服務端),對比Java和Php的實現方式

畫張圖:(傳統的java或者tomcat伺服器)

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

程式:計算機在分配資源時,都是按程式為單位                                                                       執行緒:執行緒是程式的一個個的單元(一個程式由很多個執行緒組成)                                           程式大於執行緒

圖解:比如,我們有一個客戶端或者多個客戶端,同時訪問我們的伺服器(常見的伺服器有tomcat、iis,這樣的伺服器的特點是:多執行緒;伺服器的執行空間我們一般都叫做:程式;)當我們客戶端傳送請求的時候,會請求我們的服務端,那我們的服務端會開一條所謂的執行緒來去配合,這個執行緒就相當於一個空間,這個空間會向我們的後臺(資料庫,常見的mysql資料庫,包括其他資料庫)傳送請求,這樣的話,這個伺服器會把這個請求轉發給mysql去請求我們的資料,那此時這個執行緒就不通了,你想,如果沒有接收到資料庫的結果,就卡在這了。那比如再來一個客戶端訪問我們的伺服器,再請求,再來一個執行緒把這個請求轉發給我們的資料庫,這種操作一般叫做io操作(操作多區資料庫的資料),那這個時候,發現個問題,客戶端傳送的請求越多,執行緒就越多,一般情況下執行緒處理的很快,比如說,資料庫請求成功後,把結果返回,那這個執行緒就關閉了,以前是這樣的,但是現在這個執行緒不會關閉了,現在這個tomcat轉變了策略,他會把這個執行緒進行復用,比如說,預設開20個執行緒,一個客戶端傳送請求,伺服器就開一個執行緒,但是這個執行緒不會被銷燬,再有請求的時候,這個執行緒再出來,它永遠複用用一個執行緒。

但是如果是多執行緒情況下:

  1. 可能會浪費資源(來一個客戶端請求,就開一個執行緒,浪費一個資源)
  2. 多執行緒靠的是切換時間片,這個過程中也會浪費資源
  3. 多執行緒 鎖的問題

在傳統的java或者tomcat伺服器都會有這些多執行緒的問題,所以node的好處就來了:

畫張圖:(node)

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

圖解:node的特點:單執行緒(單執行緒沒有鎖的概念,不會同時操作同一個資料),他所謂的單執行緒並不是說我們應用裡面就一條執行緒,他指的是主執行緒(主執行緒是單執行緒的),也就是說一個應用(程式)裡面可能就一條主執行緒,就是說它裡面可能也有很多條執行緒,比如其他執行緒,像setTimeout,setTimeout也算是執行緒,但是setTimeout不在我們的主執行緒裡面,而且setTimeout的好處就是:比如說,我們一個客戶端來訪問我們的服務端,這時候服務端開一個執行緒來配合,並且去訪問資料庫,但是node有個特點:非同步非阻塞,也就是說,它並不會等待這個資料庫什麼時候完成,他只告訴客戶端要什麼資料,然後它就閒下來了。他不會等著資料庫吧這個結果返回給我們客戶端,這個時候比如說:再來一個客戶端訪問伺服器,上一個客戶端就不管了,然後直接把執行緒1移到下面,這個時候,如果有很多客戶端來訪問伺服器,伺服器只需要訪問資料庫,不需要等待資料庫返回結果,知道資料庫開始返回結果了,這個執行緒1再返回去吧資料返回個客戶端。所以node可以扛幾千萬的併發,但是java只能扛個四五千萬,效能上有很大的差距。

Node在處理高併發,I/O密集場景有明顯的效能優勢

  • 高併發,是指在同一時間併發訪問伺服器
  • I/O密集指的是檔案操作、網路操作、資料庫,相對的有CPU密集,CPU密集指的是邏輯處理運算、壓縮、解壓、加密、解密
Web主要場景就是接收客戶端的請求讀取靜態資源和渲染介面,所以Node非常適合Web應用的開發。

2.Node是什麼?

Node.js是一個基於 Chrome V8 引擎的JavaScript執行環境(runtime),Node不是一門語言是讓js執行在後端的執行時,並且不包括javascript全集,因為在服務端中不包含DOMBOM,Node也提供了一些新的模組例如http,fs模組等。Node.js 使用了事件驅動非阻塞式 I/O 的模型,使其輕量又高效並且Node.js 的包管理器 npm,是全球最大的開源庫生態系統。

3.程式與執行緒

程式是作業系統分配資源和排程任務的基本單位(一個應用可能就是一個程式),執行緒是建立在程式上的一次程式執行單位,一個程式上可以有多個執行緒。最能比喻的就是我們的瀏覽器,一個瀏覽器是多程式的。

3.1 畫張圖:談談瀏覽器

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

  • 使用者介面-包括位址列、前進/後退按鈕、書籤選單等
  • 瀏覽器引擎-在使用者介面和呈現引擎之間傳送指令(瀏覽器的主程式)
  • 渲染引擎,也被稱為瀏覽器核心(瀏覽器渲染程式)
  • 一個外掛對應一個程式(第三方外掛程式)
  • GPU提高網頁瀏覽的體驗(GPU程式)

由此可見瀏覽器是多程式的,並且從我們的角度來看我們更加關心瀏覽器渲染引擎

圖解:使用者介面,瀏覽器引擎,瀏覽器渲染引擎這三個都是程式。在我們寫程式碼的時候最關心的是瀏覽器的渲染引擎,這個渲染引擎裡面又分成了:Networking(請求),Javascript Interperter(JS執行緒),UI Backend(渲染css的)。Networking(請求),Javascript Interperter(JS執行緒),UI Backend(渲染css的)這三部分都是執行緒,我們關心的有兩個執行緒:JS執行緒和UI執行緒,這兩個執行緒的關係如下:

執行緒

  • JS執行緒  UI執行緒:這兩個執行緒是同時在瀏覽器上執行的,但是有個區別:這兩個執行緒是互斥的,目的就是為了保證不產生衝突,因為ui執行緒是渲染dom,js執行緒是操作dom,兩個執行緒同時程式會衝突。所以js執行緒執行,ui執行緒就不執行;ui執行緒執行,js執行緒就不執行。
  •  JS執行緒執行的時候,ui執行緒可能再渲染,那渲染的結果放在哪呢?=>這時候,ui執行緒會把更改放在佇列中,當js執行緒空閒下來,ui執行緒再繼續渲染。

 webworker 多執行緒 *

  • 他和js主執行緒不是平級的,主執行緒可以控制webworker,webworker不能操作dom 不能獲取document,window

  寫個webworker的小列子:

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

       給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。


3.2  渲染引擎

渲染引擎內部是多執行緒的,內部包含兩個最為重要的執行緒ui執行緒和js執行緒。這裡要特別注意ui執行緒和js執行緒是互斥的,因為JS執行結果會影響到ui執行緒的結果。ui更新會被儲存在佇列中等到js執行緒空閒時立即被執行。

3.3  js單執行緒

javascript在最初設計時設計成了單執行緒,為什麼不是多執行緒呢?如果多個執行緒同時操作DOM那豈不會很混亂?這裡所謂的單執行緒指的是主執行緒是單執行緒的,所以在Node中主執行緒依舊是單執行緒的。

3.4 其他執行緒

  • 瀏覽器事件觸發執行緒(用來控制事件迴圈,存放setTimeout、瀏覽器事件、ajax的回撥函式)
  • 定時觸發器執行緒(setTimeout定時器所線上程)
  • 非同步HTTP請求執行緒(ajax請求執行緒)
單執行緒特點是節約了記憶體,並且不需要在切換執行上下文。而且單執行緒不需要管鎖的問題,這裡簡單說下鎖的概念。例如下課了大家都要去上廁所,廁所就一個,相當於所有人都要訪問同一個資源。那麼先進去的就要上鎖。而對於node來說。下課了就一個人去廁所,所以免除了鎖的問題!

4.瀏覽器中的Event Loop(面試中經常會問到的事件環)

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

  • 1.所有同步任務都在主執行緒上執行,形成一個執行棧
  • 2.主執行緒之外,還存在一個任務佇列。只要非同步任務有了執行結果,就在任務佇列之中放置一個事件。
  • 3.一旦執行棧中的所有同步任務執行完畢,系統就會讀取任務佇列,將佇列中的事件放到執行棧中依次執行
  • 4.主執行緒從任務佇列中讀取事件,這個過程是迴圈不斷的

整個的這種執行機制又稱為Event Loop(事件迴圈)


圖解:如圖,事件環分成幾部分。js分為兩部分,一個叫heap(堆),一個叫stack(棧);物件放在heap(堆)裡,常見的基礎型別和函式放在stack(棧)裡,函式執行的時候在棧裡執行。棧裡面可能會調一些Dom操作,ajax操作和setTimeout定時器。stack(棧)裡面的程式先走,走完後再走WebAPIs(非同步的方法),WebAPIs(非同步的方法)執行後的結果放在callback queue(回撥的佇列裡),也就是當棧裡面的程式走完之後,再從任務佇列中讀取事件放到棧裡面去執行,這個過程是迴圈不斷的。

小例子1:

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

圖解程式碼執行方式:先把console.log(1),console.log(2),console.log(5)放在stack(棧)中執行,然後棧裡面可能會呼叫setTimeout,但是棧裡面一定會先一次執行完也就是1,2,5;然後兩個setTimeout方法並沒有執行,會先移到佇列(callback queue)裡面去,然後當棧裡面都執行完之後,開始從從任務佇列中讀取事件往棧裡面去執行。

小列子2:

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

圖解程式碼執行方式:先把console.log(1),console.log(2),console.log(5)放在stack(棧)中執行,然後棧裡面可能會呼叫setTimeout,但是棧裡面一定會先一次執行完也就是1,2,5;然後兩個setTimeout方法並沒有執行,會先移到佇列(callback queue)裡面去,然後當棧裡面的1,2,5都執行完之後,開始從從任務佇列中讀取事件往棧裡面去執行,這時候3,4執行,並且6,7放到佇列裡面,然後3,4執行完後,再從任務佇列中讀取事件往棧裡面去執行。所以順序是1,2,5,3,4,6,7

如果設定了setTimeout的時間,那就是按setTimeout的成功時間依次執行,如圖:這裡的順序是1,2,5,4,7,3,6。也就是隻要兩個set時間不一樣的時候 ,就set時間短的裡面走完(包括set裡面的回撥函式),再走set時間慢的。如果兩個set時間一樣,就是按順序走set,走完後再按順序走set裡面的回撥。

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

小列子3:當觸發回撥函式時,會將回撥函式放到佇列中。永遠都是棧裡面執行完後再從任務佇列中讀取事件往棧裡面去執行。

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。


stack(棧)和queue(佇列)的區別:

棧方法LIFO(Last In First Out):先進後出(先進的後出),典型的就是函式呼叫。

例如:

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

程式碼走勢圖:

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

圖解:執行棧裡面最先放的是全域性作用域(程式碼執行有一個全域性文字的環境),完了再放one。完了one執行再把two放進來,完了two執行再把three放進來,一層疊一層。那麼怎麼出呢,怎麼銷燬的呢? => 最先走的肯定是three,因為two要是先銷燬了,那three的程式碼b就拿不到了,所以是先進後出(先進的後出),所以,three最先出,然後是two出,再是one出。

佇列方法FIFO(First In First Out)

(隊頭)[1,2,3,4](隊尾)   進的時候從隊尾依次進1,2,3,4  出的時候從對頭依次出1,2,3,4

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。


程式碼執行都是按棧的結果去執行的,但是我們呼叫完多執行緒的方法(WebAPIs),這些多執行緒的方法是放在佇列裡的,也就是先放到佇列裡的方法先執行。那什麼時候WebAPIs裡的方法會再執行呢? => 比如:stack(棧)裡面都走完之後,就會依次執行佇列裡的方法,當棧裡面再寫個事件,這個事件可能還會呼叫WebAPIs裡的非同步方法,那這些非同步方法會在再被呼叫的時候放在佇列裡,然後這個主執行緒(也就是stack)從任務佇列中讀取事件,這個過程是迴圈不斷的。

5.Node系統

node工作和瀏覽器中的事件環不一樣,node有自己的機制。我們先來張圖看看node是如何工作的

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

  • 1.我們寫的js程式碼會交給v8引擎進行處理
  • 2.程式碼中可能會呼叫nodeApi,node會交給libuv庫處理
  • 3.libuv通過阻塞i/o和多執行緒實現了非同步io
  • 4.通過事件驅動的方式,將結果放到事件佇列中,最終交給我們的應用。

node的特點:非同步  非阻塞i/o  node通過LIBUV這個庫自己實現的非同步,預設的情況下是沒有非同步的方法的。

圖解:我們寫的js程式碼是和v8引擎進行互動的,這個v8引擎會提供一些方法,比如setTimeout屬於我們v8引擎的方法,v8引擎裡面還會呼叫nodeAPI(常見的nodeAPI有readFile(檔案讀取),readFile是非同步的,可以讀取檔案),呼叫完nodeAPI以後,會把方法交給LIBUV非同步庫,這個非同步庫通過BLOCKING(阻塞)和WORKER THREADS(工作執行緒,這個工作執行緒是n個的),所以說node底層是多執行緒的。它是根據BLOCKING OPERATION阻塞的io和WORKER THREADS(多執行緒)實現了非同步io,最後通過EVENT LOOP事件環把結果返回給我們的應用。他會把成功的結果放在EVENT QUEUE(事件佇列)裡去,同樣會呼叫這個事件佇列裡的結果,再返回給我們的應用。

6.同步與非同步

同步和非同步關注的是訊息通知機制

  • 同步就是發出呼叫後,沒有得到結果之前,該呼叫不返回,一旦呼叫返回,就得到返回值了。 簡而言之就是呼叫者主動等待這個呼叫的結果
  • 而非同步則相反,呼叫者在發出呼叫後這個呼叫就直接返回了,所以沒有返回結果。換句話說當一個非同步過程呼叫發出後,呼叫者不會立刻得到結果,而是呼叫發出後,被呼叫者通過狀態、通知或回撥函式處理這個呼叫。

7.阻塞與非阻塞

阻塞和非阻塞關注的是程式在等待呼叫結果(訊息,返回值)時的狀態.

  • 阻塞呼叫是指呼叫結果返回之前,當前執行緒會被掛起。呼叫執行緒只有在得到結果之後才會返回。
  • 非阻塞呼叫指在不能立刻得到結果之前,該呼叫不會阻塞當前執行緒。

8.組合

同步非同步取決於被呼叫者,阻塞非阻塞取決於呼叫者

  • 同步阻塞
  • 非同步阻塞
  • 同步非阻塞
  • 非同步非阻塞(node就是非同步非阻塞)

9.巨集任務和微任務(面試經常問)

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

同步程式碼先執行 執行是在棧中執行的,然後微任務會先執行,再執行巨集任務

任務可分為巨集任務和微任務

  • macro-task(巨集任務): setTimeout, setInterval, setImmediate, I/O
  • micro-task(微任務): process.nextTick, 原生Promise(有些實現的promise將then方法放到了巨集任務中),Object.observe(已廢棄), MutationObserver不相容的

Promise.then(原始碼見到Promise就用setTimeout),then方法不應該放到巨集任務中(原始碼中寫setTimeout是迫不得已的),在我們真正瀏覽器的實現裡這個then屬於微任務的。如圖:

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

圖解:先走console.log(1),這裡的new Promise()是立即執行的,所以是同步的,由於這個then在console.log(2)後面執行的,所以不是同步,是非同步的。那這跟巨集任務和微任務有什麼關係? => 我們可以加一個setTimeout(巨集任務)對比一下,如圖:

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

結論:同步程式碼先執行  執行是在棧中執行的,然後微任務會先執行,再執行巨集任務

MutationObserver小列子:

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

給初學者的一篇文章:Node.js  Event Loop執行機制 (4,5,9面試經常會問)未完。。。待續。。。

10.什麼場合下應該考慮使用Node框架

當應用程式需要處理大量併發的輸入輸出,而在向客戶端響應之前,應用程式並不需要進行非常複雜的處理。

  • 聊天伺服器
  • 電子商務網站




相關文章