談談 Event Loop(事件迴圈)機制

Wang豔芬發表於2018-04-01

在講 Event Loop (事件迴圈)之前,我們來了解點 node 的東西,來幫助我們更加明白事件迴圈是幹什麼的

Node 是什麼

Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行環境,Node 不是一門語言,是讓 js 執行在後端的,執行時不包括 js 全集,因為在服務端中不包含 DOM 和 BOM,Node 也提供了一些新的模組,比如 http,fs等模組。

Node 解決了什麼

Node 的首要目標是提供一種簡單的,用於建立高效能伺服器的開發工具 Web 伺服器的瓶頸在於併發的使用者量,對比 Java 和 Php 的實現方式

Node在處理高併發,I/O 密集場景有明顯的效能優勢
  • 高併發,是指在同一時間併發訪問伺服器
  • I/O 密集指的是檔案操作、網路操作、資料庫,相對的有 CPU 密集,CPU 密集指的是邏輯處理運算、壓縮、解壓、加密、解密

Web 主要場景就是接收客戶端的請求讀取靜態資源和渲染介面,所以 Node 非常適合 Web 應用的開發。

程式與執行緒

程式是作業系統分配資源和排程任務的基本單位,執行緒是建立在程式上的一次程式執行單位,一個程式上可以有多個執行緒。

  1. 瀏覽器執行緒
    • 使用者介面-包括位址列、前進/後退按鈕、書籤選單等
    • 瀏覽器引擎-在使用者介面和呈現引擎之間傳送指令(瀏覽器的主程式)
    • 渲染引擎,也被稱為瀏覽器核心(瀏覽器渲染程式)
    • 一個外掛對應一個程式(第三方外掛程式)
    • GPU提高網頁瀏覽的體驗( GPU 程式)
  2. 瀏覽器渲染引擎
    • 渲染引擎內部是多執行緒的,內部包含 ui 執行緒和 js 執行緒
    • js 執行緒 ui 執行緒 這兩個執行緒互斥的,目的就是為了保證不產生衝突。
    • ui 執行緒會把更改的放到佇列中,當 js 執行緒空閒下來的時候,ui 執行緒在繼續渲染
  3. js 單執行緒
    • js 是單執行緒,為什麼呢?如果多個執行緒同時操作 DOM ,哪頁面不會很混亂?這裡所謂的單執行緒指的是主執行緒是單執行緒的,所以在 Node 中主執行緒依舊是單執行緒的。
  4. webworker 多執行緒
    • 它和 js 主執行緒不是平級的,主執行緒可以控制 webworker,但是 webworker不能操作 DOM,不能獲取 document,window
  5. 其他執行緒
    • 瀏覽器事件觸發執行緒(用來控制事件迴圈,存放 setTimeout、瀏覽器事件、ajax 的回撥函式)
    • 定時觸發器執行緒(setTimeout 定時器所線上程)
    • 非同步 HTTP 請求執行緒(ajax 請求執行緒)

單執行緒特點是節約了記憶體,並且不需要在切換執行上下文。而且單執行緒不需要管鎖的問題,所謂 鎖,在 java 裡才有鎖的概念,所以我們不用細研究

瀏覽器中的 Event Loop

2.jpg

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

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

node 中的 Event Loop

如圖(圖片是借鑑的):

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

同步,非同步 阻塞和非阻塞

  • 阻塞和非阻塞指的是呼叫者的狀態,關注的是程式在等待呼叫結果時的狀態
  • 同步和非同步指的是被呼叫者是如何通知的,關注的是訊息通知機制

巨集任務和微任務

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

同步程式碼先執行,執行是在棧中執行的,微任務大於巨集任務,微任務會先執行(棧),巨集任務後執行(佇列)

講到這裡,敲幾行程式碼來總結下我們上面講到的知識點把

《1》巨集任務,微任務在瀏覽器和 node 環境執行順序不同

// 這個列子裡面,包含了巨集任務,微任務,分別看看瀏覽器和node 列印的結果
console.log(1)
// 棧
setTimeout(function(){
    console.log(2)
    // 微任務
    Promise.resolve(100).then(function(){
        console.log('promise')
    })
})
// 棧
let promise = new Promise(function(resolve, reject){
    console.log(7)
    resolve(100)
}).then(function(data){
    // 微任務
    console.log(data)
})
// 棧
setTimeout(function(){
    console.log(3)
})
console.log(5)
// 瀏覽器結果:1 7 5 100 2 promise 3
// node 結果:  1 7 5 100 2 3 promise
複製程式碼

瀏覽器和 node 環境執行順序不同,瀏覽器是先把一個棧以及棧中的微任務走完,才會走下一個棧。node 環境裡面是把所以棧走完,才走微任務

《2》setTimeout setImmediate 都是巨集任務,誰優先執行呢?

setTimeout(function(){
    console.log('timeout')
})
setImmediate(function(){
    console.log('setImmediate')
})
// 結果列印:timeout setImmediate
複製程式碼

setTimeout setImmediate 這兩個取決於 node 的執行時間

《3》nextTick 和 then 都屬於微任務,誰優先執行呢?

process.nextTick(function(){
    console.log('nextTick')
})
Promise.resolve().then(function(){
    console.log('then')
})
// 結果列印:nextTick then

// 再加一個巨集任務呢
setImmediate(function(){
    console.log('setImmediate')
})
// 結果列印:nextTick  then  setImmediate
複製程式碼

nextTick 會比 其他微任務、巨集任務執行快

《4》i/o 檔案操作(巨集任務),搭配微任務,誰優先執行呢?

let fs = require('fs');
fs.readFile('./1/log',function(){
    console.log('fs')
})
process.nextTick(function(){
    console.log('text')
})
// 結果列印:text  fs
複製程式碼

i/o 檔案操作(巨集任務), 如果有微任務,先執行微任務,在執行檔案讀取

相關文章