node事件迴圈和訊息佇列簡單分析
node的好處毋庸置疑,事件驅動,非同步非阻塞I/O,以及處理高併發的能力深入人心,因此大家喜歡用node做一些小型後臺服務或者作為中間層和其他服務配合完成一些大型應用場景。
什麼是非同步?
非同步和同步應該是經常談的一個話題了。同步的概念很簡單,自上而下依次執行,必須等上邊執行完下邊才會執行。而非同步可以先提交一個命令,中間可以去執行別的事務,而當執行完之後回過頭來返回之前的任務。
舉個栗子:
你很幸運,找了一個漂亮的女朋友,有一天你的女朋友發簡訊問你晚上看什麼電影?但你並不知道看什麼,馬上開啟電腦查了一下近期熱播的電影,這其中你女朋友一直在等你,這就是同步
而非同步呢?還是你女朋友發簡訊問你看什麼電影,你跟她說: 你先等會吧吧,等我查一下,查好之後你回頭打電話告訴了她。這就是非同步。
從而我們能看出同步和非同步的一些特點:
1.必須發生在兩個物件身上。(你和你女朋友)
2.必須發生一些事情。(看電影)
不同的就是:同步就是依次執行,執行完一個之後在執行另一個,直到執行完成,而非同步就是先執行一個,可能沒有執行完成再去執行另一個,等第一個執行完成後再返回結果
那麼,為什麼需要非同步呢?
答案很明顯,為了提高辦事的效率,CPU計算速度和磁碟的讀寫速度差太遠了,磁碟供不應求,因此有了計算機的儲存系統的分層設計,平衡了效率和成本。可以說懶惰推動人類的進步,任何可以降低花費時間而達到同等功效的方法肯定會被優先採用。
傳送簡訊時等待對方回覆的時間純粹的浪費掉了,CPU寫入磁碟等待返回的結果的等待時間也被無情的消耗了,這是一個講究效率的時代完全不能忍受的,因此讓員工一直處於忙碌狀態,最大限度的榨取員工價值是老闆追求的,讓CPU和磁碟都不停的滿
負荷處理事務也是效率需要的。因此,非同步處理出現了。
那什麼是非同步IO?
非同步IO是指作業系統提供的IO(資料進出)的能力,比如鍵盤輸入,對應到顯示器上會有專門的資料輸出介面,這就是我們生活中可見的IO能力;這個介面在向下會進入到作業系統這個層面,在作業系統中,會提供諸多的能力,比如:磁碟的讀寫,DNS
的查詢,資料庫的連線啊,網路請求的處理,等等;
在不同的作業系統層面,表現的不一致。有的是非同步非阻塞的;有的是同步的阻塞的,無論如何,我們都可以看做是上層應用於下層系統之間的資料互動;上層依賴於下層,但是反過來,上層也可以對下層提供的這些能力進行改造;如果這種操作是非同步
的,非阻塞的,那麼這種就是非同步非阻塞的非同步IO模型;如果是同步的阻塞的,那麼就是同步IO模型;
koa
就是一個上層的web服務框架,全部由js實現,他有作業系統之間的互動,全部通過nodejs
來實現;如nodejs
的 readFile
就是一個非同步非阻塞的介面,readFileSync
就是一個同步阻塞介面。
什麼是事件迴圈?
事件迴圈是指Node.js
執行非阻塞I/O操作,儘管JavaScript是單執行緒的,但由於大多數核心都是多執行緒的,node.js會盡可能將操作裝載到系統核心。因此它們可以處理在後臺執行的多個操作。當其中一個操作完成時,核心會告訴Node.js,以便node.js可
以將相應的回撥新增到輪詢佇列中以最終執行。也就是說,js是單執行緒的,但是node執行的時候其實是多執行緒的。(個人理解)
而訊息佇列是一個先進先出的佇列,它裡面存放著各種訊息。
V8引擎
我們常說的Chrome引擎和nodejs引擎就是V8引擎,他大致由以下組成:
這個引擎由記憶體堆和呼叫棧組成,記憶體堆就是負責進行記憶體分配,比如變數賦值,呼叫棧就是程式碼執行的地方。
呼叫棧中順序執行主執行緒的程式碼,當呼叫棧中為空時,js引擎會去訊息佇列取訊息。取到後就執行。JavaScript是單執行緒的程式語言,意味著它有一個單一的呼叫棧。因此它只能在同一時間做一件事情。呼叫棧是一種資料結構,它基本上記錄了我們 在程式中的什麼位置。如果我們步入一個函式中,我們會把這些資料放在堆疊的頂部。如果我們從一個函式中返回,這些資料將會從棧頂彈出。這就是堆疊的用途。呼叫棧中的每個條目叫做棧幀。 堆和棧的區別就是先進先出,一個先進後出。 當js執行時:
我們經常使用的一些API並不是js引擎中提供的,例如定時器setTimeout。
它們其實是在瀏覽器中提供的,也就是執行時提供的,因此,實際上除了JavaScript引擎以外,還有其他的元件。
其中有個元件就是由瀏覽器提供的,叫Web APIs,像DOM,AJAX,setTimeout等等。
然後還有就是非常受歡迎的事件迴圈和回撥佇列。
執行時負責給引擎執行緒傳送訊息,只負責生產訊息,不負責取訊息。
訊息佇列
主執行緒在執行過程中遇到了非同步任務,就發起函式或者稱為註冊函式,通過event loop執行緒通知相應的工作執行緒(如ajax,dom,setTimout等),同時主執行緒繼續向後執行,不會等待。等到工作執行緒完成了任務,eventloop執行緒會將訊息新增到訊息佇列
中,如果此時主執行緒上呼叫棧為空就執行訊息佇列中排在最前面的訊息,依次執行。
新的訊息進入佇列的時候,會自動排在佇列的尾端。
單執行緒意味著js任務需要排隊,如果前一個任務出現大量的耗時操作,後面的任務得不到執行,任務的積累會導致頁面的“假死”。這也是js程式設計一直在強調需要回避的“坑”。
主執行緒會迴圈上述步驟,事件迴圈就是主執行緒重複從訊息佇列中取訊息、執行的過程。
需要注意的是 GUI渲染執行緒與JS引擎是互斥的,當JS引擎執行時GUI執行緒會被掛起,GUI更新會被儲存在一個佇列中等到JS引擎空閒時立即被執行。因此頁面渲染都是在js引擎主執行緒呼叫棧為空時進行的。
其實 事件迴圈 機制和 訊息佇列 的維護是由事件觸發執行緒控制的。
事件觸發執行緒 同樣是瀏覽器渲染引擎提供的,它會維護一個 訊息佇列。
JS引擎執行緒遇到非同步(DOM事件監聽、網路請求、setTimeout計時器等...),會交給相應的執行緒單獨去維護非同步任務,等待某個時機(計時器結束、網路請求成功、使用者點選DOM),然後由 事件觸發執行緒 將非同步對應的 回撥函式 加入到訊息佇列中,消
息佇列中的回撥函式等待被執行。
同時,JS引擎執行緒會維護一個 執行棧,同步程式碼會依次加入執行棧然後執行,結束會退出執行棧。
如果執行棧裡的任務執行完成,即執行棧為空的時候(即JS引擎執行緒空閒),事件觸發執行緒才會從訊息佇列取出一個任務(即非同步的回撥函式)放入執行棧中執行。
(以上是我個人理解還有從網上的摘抄[dog])