js Event Loop 執行機制

satomiyoyi07發表於2018-08-06

Event Loop,事件環,執行緒程式。這些概念對初識前端的同學來說可能會一頭霧水。而且執行js程式碼的執行環境除了瀏覽器還有node。因此不同環境處理Event Loop又變得不同,十分容易混淆。如果你有這樣的疑問。下文將給你一個清晰的解釋。

概念梳理

首先我們簡化一下概念,把程式,執行緒,事件環,這些概念梳理一下。清晰了概念後面用到的時候就會有共鳴。

程式和執行緒基本概念

拿出在教科書裡的概念:

1、排程:執行緒作為排程和分配的基本單位,程式作為擁有資源的基本單位;

2、併發性:不僅程式之間可以併發執行,同一個程式的多個執行緒之間也可併發執行;

3、擁有資源:程式是擁有資源的一個獨立單位,執行緒不擁有系統資源,但可以訪問隸屬於程式的資源;

4、系統開銷:在建立或撤消程式時,由於系統都要為之分配和回收資源,導致系統的開銷明顯大於建立或撤消執行緒時的開銷。

程式和執行緒的關係:

  1. 一個執行緒只能屬於一個程式,而一個程式可以有多個執行緒,但至少有一個執行緒;
  2. 資源分配給程式,同一程式的所有執行緒共享該程式的所有資源;
  3. 處理機分給執行緒,即真正在處理機上執行的是執行緒;
  4. 執行緒在執行過程中,需要協作同步。不同程式的執行緒間要利用訊息通訊的辦法實現同步。執行緒是指程式內的一個執行單元,也是程式內的可排程實體。
    第一次看可能並沒什麼共鳴。但是帶著最基本的想法,一個程式可以有多個執行緒,執行緒之間可以相互通訊。這兩點,就足夠你理解後續事件環的知識。

瀏覽器中的程式和執行緒和Event Loop

瀏覽器的程式

  1. 從開啟瀏覽器開始,開啟瀏覽器,我們首先看到的是,使用者介面,這裡有搜尋框,顯示區,還有收藏夾等等。這些會分配一個程式。
  2. 我們看到瀏覽器自己會實現一些本地儲存,cookie等,這些操作也需要分配一個程式。

js Event Loop 執行機制
3. 開啟一個瀏覽器的tab頁,他如果想要執行就需要系統分配給他cpu和記憶體資源,因此他需要分配一個程式。對應上面的概念“程式是擁有資源的基本單位”。因此每開啟一個tab就對應一個新的程式。從資源管理器中程式可以看到,chrome佔用多個程式。(有些系統會對程式進行整合,win10下可能看到的效果不同)

js Event Loop 執行機制
眼見為實,我們才能說瀏覽器是多執行緒的。那我們用視覺化的角度看一下瀏覽器的這個程式和執行緒結構

js Event Loop 執行機制
從圖中看黃色的圓角框裡包裹的都是程式。藍色的直角框裡包裹的都是瀏覽器渲染引擎(瀏覽器核心)所包含的執行緒。對應上面的概念“一個程式可以有多個執行緒,但至少有一個執行緒”。前三個程式剛剛在1-3裡都說過了。 介紹了剛剛那麼多我們前端的操作其實都是在3中瀏覽器渲染引擎中處理。真正幹活的就是執行緒。對應上面的概念“處理機分給執行緒,即真正在處理機上執行的是執行緒”。

瀏覽器核心的執行緒

接下來看一下瀏覽器引擎(程式)中包含哪些執行緒

  • UI渲染執行緒 負責渲染瀏覽器介面,解析HTML,CSS,構建DOM樹和RenderObject樹,佈局和繪製等。 當介面需要重繪(Repaint)或由於某種操作引發迴流(reflow)時,該執行緒就會執行

注意:UI渲染執行緒與JS引擎執行緒是互斥的,當JS引擎執行時GUI執行緒會被掛起(相當於被凍結了),UI更新會被儲存在一個佇列中等到JS引擎空閒時立即被執行。

  • js引擎執行緒(JS解析執行緒) 也稱為JS核心,負責處理Javascript指令碼程式。(例如V8引擎) JS引擎執行緒負責解析Javascript指令碼,執行程式碼。 JS引擎一直等待著任務佇列中任務的到來,然後加以處理,一個Tab頁(renderer程式)中無論什麼時候都__只有一個JS執行緒在執行JS程式__

同樣注意:UI渲染執行緒與JS引擎執行緒是互斥的,所以如果JS執行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染載入阻塞。

  • 事件觸發執行緒 __歸屬於瀏覽器__而不是JS引擎,用來控制事件迴圈(可以理解,JS引擎自己都忙不過來,需要瀏覽器另開執行緒協助) 當JS引擎執行程式碼塊如setTimeOut時(也可來自瀏覽器核心的其他執行緒,如滑鼠點選、AJAX非同步請求等),會將對應任務新增到事件執行緒中 當對應的事件符合觸發條件被觸發時,該執行緒會把事件新增到待處理佇列的隊尾,等待JS引擎的處理

注意:由於JS的單執行緒關係,所以這些待處理佇列中的事件都得排隊等待JS引擎處理(當JS引擎空閒時才會去執行)

  • 定時觸發器執行緒 傳說中的setInterval與setTimeout所線上程 瀏覽器定時計數器並不是由JavaScript引擎計數的,(因為JavaScript引擎是單執行緒的, 如果處於阻塞執行緒狀態就會影響記計時的準確) 因此通過單獨執行緒來計時並觸發定時(計時完畢後,新增到事件佇列中,等待JS引擎空閒後執行)

注意:W3C在HTML標準中規定,規定要求setTimeout中低於4ms的時間間隔算為4ms。

  • 非同步http請求執行緒 在XMLHttpRequest在連線後是通過瀏覽器新開一個執行緒請求 將檢測到狀態變更時,如果設定有回撥函式,非同步執行緒就產生狀態變更事件,將這個回撥再放入事件佇列中。再由JavaScript引擎執行。

js渲染引擎的Event Loop

以上執行緒,每個拿出來都可以詳細的說上一篇。Event Loop涉及到的JS引擎的一些執行機制的分析。我們可以將這些執行緒理解為,

  • 一個主程式就是js引擎,其他均為輔助的執行緒。
  • 主程式存在一個執行棧,事件觸發執行緒維護一個訊息佇列
  • 同步任務在執行棧中執行,非同步任務在滿足條件後加入到訊息佇列中,等待執行。
  • 先執行棧中的任務,執行完畢後,檢查佇列是否為空,不為空,將佇列中的任務壓入執行棧中執行。直到棧和佇列均為空。 js渲染引擎的Event Loop如下圖

js Event Loop 執行機制
這時候拿出幾道題看一下會更清晰 題目1:

setTimeout(function(){
    console.log(0)
},500)
setTimeout(function(){
    console.log(1)
},1000)
setTimeout(function(){
    console.log(2)
},2000)

for(;;){

}
複製程式碼

上面這段程式碼用於不會有輸出,同步程式碼死迴圈阻塞了執行棧。雖然定時後回撥加入執行佇列,但是異永遠不會執行。
題目二:

setTimeout(function(){
    console.log('setTimeout1');
    Promise.resolve().then(()=>{
        console.log('then1');
    });
},0)
Promise.resolve().then(()=>{
    console.log('then2');
    Promise.resolve().then(()=>{
        console.log('then3');
    })
    setTimeout(function(){
        console.log('setTimeout2');
    },0)
})
複製程式碼

答案:then2 then3 setTimeout1 then1 setTimeout2
首先在題目中出現了es6的promise,他的出現讓原來我們理解的__事件環產生了一些不同__。 為什麼呢?因為Promise裡有了一個一個新的概念:microtask 此時JS中分為__兩種任務型別__:macrotask和microtask,在ECMAScript中,microtask稱為jobs,macrotask可稱為task

微任務和巨集任務

首先說明,是以__瀏覽器為處理環境__下的執行邏輯 瀏覽器環境下的微任務和巨集任務有哪些 巨集任務:setTimeout setImmediate MessageChannel 微任務:Promise.then MutationObserver
記住兩點:

  • 微任務在巨集任務之前的執行,先執行 執行棧中的內容 執行後 清空微任務
  • 每次取一個巨集任務 就去清空微任務,之後再去取巨集任務

然後題目入手分析巨集任務和微任務的執行

js Event Loop 執行機制

  • setTimeout1放入巨集任務執行佇列中,微任務then2放入微任務佇列中,棧為空,優先執行微任務,則先執行then2。
  • then2之後執行後,接下來存在微任務then3。將then3放入微任務佇列中。
  • 接下來setTimeout2加入到巨集任務佇列中。
  • 此時執行棧為空,執行then3。
  • 微任務全部執行完畢後,執行巨集任務setTimeout1,執行發現微任務then1,放置到微任務佇列中。
  • setTimeout1巨集任務執行完,再次清空微任務佇列,執行then1
  • 微任務全部執行完畢後,執行巨集任務setTimeout2。程式結束。

node執行環境中的程式和執行緒

Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行環境。他的目標就是解析js程式碼,讓他能執行起來。

node js 是單執行緒的

和瀏覽器環境下類似,他有一個解析js的主執行緒,其他執行緒作為輔助,但是因為不涉及操作dom,ui執行緒就不存在了。(各個執行緒的概念參考瀏覽器環境下的執行緒)
單執行緒在瀏覽器執行環境中的弊端體現在阻塞頁面執行。
那麼node作為後端服務,單執行緒有什麼利弊?
優點:

  1. 避免頻繁建立、切換程式的開銷,使執行速度更加迅速。
  2. 資源佔用小
  3. 執行緒安全,不用擔心同一變數同時被多個執行緒進行讀寫而造成的程式崩潰。 缺點:
  4. 不適合大量的計算和壓縮等cpu密集型的操作,會造成阻塞。

node下Event Loop

事件環的整體還是不變的,執行棧,訊息佇列,api。不同的是,node下的訊息佇列有所不同

js Event Loop 執行機制
分析一下node下的訊息佇列

  • 為微任務,定時器,io,setImmidiate分別分配訊息佇列
  • 先檢查定時器佇列,如果有內容,則全部清空
  • 從時間佇列切換到io佇列的過程中,檢查微任務,如果有則情況微任務。
  • io佇列執行完成,如果有check佇列的內容,則執行。否則繼續檢查定時器佇列。
  • 完成閉環

從一個題目入手感受一下node環境和瀏覽器環境下的不同

setTimeout(() => {
    console.log('timeout1');
    Promise.resolve().then(() => {
        console.log('promise');
    });

}, 0)
setTimeout(() => {
    console.log('timeout2');
}, 0)
複製程式碼

瀏覽器下的結果:timeout1 promise timeout2
node下的結果:timout1 timeout2 promise

微任務和巨集任務

node環境下的微任務和巨集任務有哪些 巨集任務:setTimeout setImmediate 微任務:Promise.then process.nextTick
題目三可以很好的分析node環境下的任務執行 node環境下執行流程

  • 首先遇到兩個巨集任務,均放入到時間佇列裡。
  • 執行時間佇列裡第一個巨集任務時timeout1,遇到微任務promise,放到微任務佇列中
  • 此時時間佇列還未清空,繼續執行完成所有時間佇列裡的任務。執行timout2
  • 在切換io佇列時檢查微任務,有則執行清空微任務。執行promise。
    瀏覽器環境下執行流程
  • 首先遇到兩個巨集任務,均放入到巨集任務佇列裡。
  • 執行時間佇列裡第一個巨集任務時timeout1,遇到微任務promise,放到微任務佇列中
  • timout1執行完成檢查微任務,有內容則執行清空,執行promise。
  • 清空微任務後再執行巨集任務。執行timeout2

注意:同樣是微任務,process.nextTick,優於promise.then先執行

Promise.resolve().then(() => {
    console.log('then')
})
process.nextTick(() => {
    console.log('nextTick')
});
//nextTick then
複製程式碼

注意:同樣是巨集任務,setTimeout和setImediate執行的先後順序是不確定的,依賴於執行棧執行的速度。

setImmediate(function () {
    console.log('setImmediate')
});
setTimeout(function () {
    console.log('setTimeout')
}, 0); // ->4
複製程式碼

js Event Loop 執行機制

js Event Loop 執行機制
但是在如下場景下是有固定輸出的

let fs = require('fs');
fs.readFile('./gitignore', function () { // io的下一個事件佇列是check階段
    setImmediate(function () {
        console.log('setImmediate')
    });
    setTimeout(function () {
        console.log('setTimeout')
    }, 0); // ->4
})

複製程式碼

給個提示,讀檔案是io操作,io執行之後首先要check,check之後或沒有check內容再去檢查定時佇列。 那麼結果就留給大家自行分析了。

總結

希望這篇文章能給初識js的你一個清晰的大框,也是梳理我自己的知識。可能我理解的也很粗淺,有錯誤的地方,希望大家幫忙指正。

參考文獻

  1. 從瀏覽器多程式到JS單執行緒,JS執行機制最全面的一次梳理
  2. Node.js的執行緒和程式詳解

相關文章