淺談JS中的非同步和單執行緒

original_galaxy發表於2018-08-20

為何js引擎是單執行緒的?

假如js引擎為多執行緒,DOM操作可能很容易會出現混亂錯誤的情況:比如某個時刻a執行緒要操作a節點時,執行緒b在時刻a之前已將a節點刪除了,這時便會出問題。

非同步可以避免主執行緒阻塞,所以對於耗時/不確定的操作,使用非同步是很好的選擇。常見的有:處理ajax請求的執行緒、處理DOM事件的執行緒、定時器執行緒、讀寫檔案的執行緒等。

非同步執行緒執行完畢後,會通知主執行緒執行相應的回撥函式,這個通知機制的實現,如下:

訊息(任務)佇列與事件迴圈(event loop)

  • 訊息佇列:訊息佇列是一個先進先出的佇列,它裡面存放著各種訊息。
  • 事件迴圈:事件迴圈是指主執行緒重複從訊息佇列中取訊息、執行的過程。

實際上,主執行緒只會做一件事情,就是從訊息佇列裡面取訊息、執行訊息,再取訊息、再執行。當訊息佇列為空時,就會等待直到訊息佇列變成非空。而且主執行緒只有在將當前的訊息執行完成後,才會去取下一個訊息。這種機制就叫做事件迴圈機制,取一個訊息並執行的過程叫做一次迴圈。

"任務佇列"中的事件,除了IO裝置的事件以外,還包括一些使用者產生的事件(比如滑鼠點選、頁面滾動等等)。只要指定過回撥函式,這些事件發生時就會進入"任務佇列",等待主執行緒讀取。

"任務佇列"是一個先進先出的資料結構,排在前面的事件,優先被主執行緒讀取。主執行緒的讀取過程基本上是自動的,只要執行棧一清空,"任務佇列"上第一位的事件就自動進入主執行緒。但是,由於存在"定時器"功能,主執行緒首先要檢查一下執行時間,某些事件只有到了規定的時間,才能返回主執行緒。

淺談JS中的非同步和單執行緒
將邏輯用程式碼表示:

while(true) {
    var message = queue.get();
    execute(message);
}
複製程式碼

訊息佇列中的訊息具體是什麼呢?訊息的具體結構是與具體的實現相關的,但是為了簡單起見,我們可以認為:

訊息就是註冊非同步任務時新增的回撥函式。(可能是不對的)
複製程式碼

淺談JS中的非同步和單執行緒

從生產者與消費者的角度看,非同步過程是這樣的:

  • 非同步執行緒是生產者,主執行緒是消費者(只有一個消費者)。非同步執行緒執行非同步任務,執行完成後把對應的回撥函式封裝成一條訊息放到訊息佇列中;主執行緒不斷地從訊息佇列中取訊息並執行,當訊息佇列空時主執行緒阻塞,直到訊息佇列再次非空。

同步可以保證順序一致,但是容易導致阻塞;非同步可以解決阻塞問題,但是會改變順序性。

參考文獻

JavaScript:徹底理解同步、非同步和事件迴圈(Event Loop)

JavaScript 執行機制詳解:再談Event Loop

相關文章