Javascript 執行機制詳解,Event Loop

ngaiwe發表於2018-01-18

單執行緒

javascript為什麼是單執行緒語言,原因在於如果是多執行緒,當一個執行緒對DOM節點做新增內容操作的時候,另一個執行緒要刪除這個DOM節點,這個時候,瀏覽器應該怎麼選擇,這就造成了混亂,為了解決這類問題,在一開始的時候,javascript就採用單執行緒模式。

在後面H5出的web worker標準的時候,看似是多執行緒,其實是在一個主執行緒來控制其他執行緒,而且不能操作DOM,所以本質還是單執行緒

任務佇列

任務可以分為兩種,一種為同步,另一種為非同步(具有回撥函式)。如下圖:

Javascript 執行機制詳解,Event Loop
所有的同步任務都在主執行緒上執行,形成一個執行棧 stack。當所有同步任務執行完畢後,它會去執行microtask queue中的非同步任務(nextTick,Promise),將他們全部執行。主執行緒之外還有一個任務佇列task queue,當有非同步任務(DOM,AJAX,setTimeout,setImmediate)有結果的時候,就在任務佇列裡放一個事件,一旦執行棧和microtask queue任務執行完畢,系統就會讀取任務佇列,將取出排在最前面的事件加入執行棧執行,這種機制就是任務佇列。

Event Loop

主執行緒在任務佇列中讀取事件,這個過程是迴圈不斷地,所以這種執行機制叫做Event Loop(事件迴圈)

nextTick、setImmediate、setTimeout

nextTick是在執行棧同步程式碼結束之後,下一次Event Loop(任務佇列)執行之前。當所有同步任務執行完,會在queue中執行nextTick,無論nextTick有多少層回撥,都會執行完畢後再去任務佇列,所以會造成一直停留在當前執行棧,無法執行任務佇列,請看下面程式碼

process.nextTick(function () {
    console.log('nextTick1');
    process.nextTick(function (){console.log('nextTick2')});
});
  
setTimeout(function timeout() {
    console.log('setTimeout');
}, 0)
複製程式碼

執行完畢後輸出nextTick1、nextTick2、setTimeout,原因是nextTick是在當前執行棧末尾執行,而setTimeout是在下次任務佇列在執行

setImmediate方法是在Event Loop(任務佇列)末尾,也就是下一次Event Loop時執行。 setTimeout方法是按照執行時間,放入任務佇列,有時快與setImmediate有時慢。請看以下程式碼

setImmediate(function () {
    console.log('setImmediate1');
    setImmediate(function (){console.log('setImmediate2')});
});
  
setTimeout(function timeout() {
    console.log('setTimeout');
}, 0);
複製程式碼

這段程式碼執行完可能是setImmediate1、setTimeout、setImmediate2,也可能是setTimeout、setImmediate1、setImmediate2,原因是setTimeout和setImmediate1都是在下次Event Loop中觸發,所以先後不確定,但是setImmediate2肯定是最後,因為他是在setImmediate1任務佇列之後,也就是下下次Event Loop執行

Node.js的Event Loop

Node.js也是單執行緒的Event Loop但是和瀏覽器有些區別,如圖所示,

1.先通過Chrom V8引擎解析Javascript指令碼

2.解析完畢後呼叫Node API

3.LIBUV庫負責Node API的執行,將不同任務分配給不同的執行緒,形成一個Event Loop(任務佇列)

4.最後Chrom V8引擎將結果返回給使用者

Javascript 執行機制詳解,Event Loop

Node.js Event Loop原理

node.js的特點是事件驅動,非阻塞單執行緒。當應用程式需要I/O操作的時候,執行緒並不會阻塞,而是把I/O操作交給底層庫(LIBUV)。此時node執行緒會去處理其他任務,當底層庫處理完I/O操作後,會將主動權交還給Node執行緒,所以Event Loop的用處是排程執行緒,例如:當底層庫處理I/O操作後排程Node執行緒處理後續工作,所以雖然node是單執行緒,但是底層庫處理操作依然是多執行緒

Node Event Loop的事件處理機制

   ┌───────────────────────┐

┌─>│        timers         │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

│  │     I/O callbacks     │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

│  │     idle, prepare     │

│  └──────────┬────────────┘      ┌───────────────┐

│  ┌──────────┴────────────┐      │   incoming:   │

│  │         poll          │<─────┤  connections, │

│  └──────────┬────────────┘      │   data, etc.  │

│  ┌──────────┴────────────┐      └───────────────┘

│  │        check          │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

└──┤    close callbacks    │

   └───────────────────────┘
複製程式碼

上面處理階段都是按照先進先出的規則執行回撥函式,按順序執行,直到佇列為空或是該階段執行的回撥函式達到該階段所允許一次執行回撥函式的最大限制後,才會將操作權移交給下一階段。

  • timers: 用來檢查setTimeout()和setInterval()定時器是否到期,如果到期則執行它,否則下一階段
  • I/O callbacks: 用來處理timers階段、setImmediate、和TCP他們的異常回撥函式或者error
  • idle, prepare: nodejs內部函式呼叫,在迴圈被I/O阻塞之前prepare回撥就會立即呼叫
  • poll: 用來監聽fd的事件的,比如socket的可讀,可寫,檔案的可讀可等等
  • check: setImmediate()函式只會在這個階段執行
  • close callbacks: 執行一些諸如關閉事件的回撥函式,如socket.on('close', ...)

具體分析,看下圖:

1.當setTimeout時間最小,讀取檔案不存在的時候

Javascript 執行機制詳解,Event Loop
如圖所示,分別是nextTick、readFile、setTimeout、setImmediate,然而現在並沒有1.txt和2.txt檔案,輸出結果是next Tick、setTimeout、readFile、setImmediate,在event loop中先判斷的是timeers,最先出書next Tick因為process.nextTick的實現是基於v8 MicroTask(是在當前js call stack 中沒有可執行程式碼才會執行的佇列,低於js call stack 程式碼,但高於事件迴圈,不屬於Event Loop,上面javascript的Event Loop介紹過了,所以最先輸出。然後開始走Event Loop,第一階段是timers,判斷setTimeout到期,所以輸出setTimeout,進入下一階段,poll將I/O操作權交出,新執行緒操作,但是並沒有相關讀取檔案,所以直接返回回撥函式,所以處處readFile,最後到check階段,輸出setImmediate

2.當setTimeout時間最小,讀取檔案存在的時候

Javascript 執行機制詳解,Event Loop
如圖所示,分別是nextTick、setTimeout、setImmediate、readFile,這次readFile在最後面,是因為檔案存在,執行到poll階段的時候,執行I/O操作,node執行緒開始執行check階段,當交出的I/O操作結束後,返回給Event Loop所以再執行readFile的回撥函式,所以他在最後面

3.當setTimeout時間為100毫秒,讀取檔案不存在的時候

Javascript 執行機制詳解,Event Loop
如圖所示,分別是nextTick、readFile、setImmediate、setTimeout,它和1不同的地方是setTimeout排在最後了,這是因為在執行timers的時候,setTimeout沒有到期,所以直接執行下一階段,當執行完poll的時候,會去執行檢視定時器有沒有到期,如果沒有下一次Event Loop再次檢視,知道定時器到期,所以他在最後面

4.當setTimeout時間為100毫秒,讀取檔案存在的時候

Javascript 執行機制詳解,Event Loop
如圖所示,分別是nextTick、setImmediate、readFile、setTimeout,它和2的區別是setTimeout在最後,原因和3一樣。

總結一下

1.javascript和node.js都是單執行緒,但是node底層是多執行緒操作

2.Event Loop —— 任務佇列

3.當同時設定nextTick, setImmediate, setTimeout時一定是nextTick先執行,nextTick不屬於Event LOop,它屬於v8的micro tasks,並且會阻塞Event Loop

4.setImmediate,setTimeout屬於Event Loop但是,直接階段不同

相關文章