Event Loop(事件機制)知多少

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

 

Node 解決了什麼

Web 服務器的瓶頸在於併發的用戶量。Node 的首要目標是提供一種簡單的,用於創建高性能服務器的開發工具。

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 中主線程依舊是單線程的。
    • 因爲是單線程,所以所有任務都需要排隊,前一個任務結束,後一個任務才能執行,如果前一個任務花費時間較長,後一個任務等待時間也隨之變長。
    • js可以做到先把等待中的任務先放一邊晾着,去處理後面的任務
    • 於是所有任務可以分爲兩種,一種是同步任務,另一種是異步任務:同步任務很簡單,前面的任務完成後面才能執行,一個接一個地執行任務。異步任務不佔用主線程,直接進入“任務隊列”中,等任務隊列通知主線程,某個任務可以執行了,纔會進入主線程執行。
  4. webworker 多線程
    • 它和 js 主線程不是平級的,主線程可以控制 webworker,但是 webworker不能操作 DOM,不能獲取 document,window
  5. 其他線程
    • 瀏覽器事件觸發線程(用來控制事件循環,存放 setTimeout、瀏覽器事件、ajax 的回調函數)
    • 定時觸發器線程(setTimeout 定時器所在線程)
    • 異步 HTTP 請求線程(ajax 請求線程)

單線程特點是節約了內存,並且不需要再切換執行上下文。

 

異步執行的運行機制

主线ç¨åä»»å¡éå.jpg

  • 所有同步任務都在主線程上執行,形成一個執行棧。
  • 主線程之外,還存在一個“任務隊列”。只要異步任務有了運行結果,就在“任務隊列”中放置一個事件。
  • 一旦執行棧中的所有同步任務都執行完畢,就會去“任務隊列”中讀取新的任務放到執行棧中,再依次執行任務。
  • 只要主線程空了,就會讀取任務隊列,這就是js的運行機制。這個過程會不斷地重複。

 

再說說事件和回調函數

  • 任務隊列其實存放的是事件的隊列,主程序讀取任務隊列,其實就是在讀有哪些事件罷了
  • 只要指定過回調函數,這些事件發生時就會進入任務隊列中,等待主線程讀取
  • 異步任務必須指定回調函數,當主線程執行異步任務時,其實就是在執行對應的回調函數
  • 任務隊列是一個先進先出的數據結構,排在前面的先執行。當調用棧中的任務空了後,主線程會自動調用任務隊列裏的任務執行

 

來看看Event Loop

  • 主線程從任務隊列中讀取任務,這個過程是不斷重複的,所以被稱爲Event Loop(事件循環),從字面意思就清楚了
  • 再來看一張圖

æµè§å¨ä¸­çEvent Loop.png

上圖中,主線程產生了heap(堆)和stack(棧),棧中的代碼調用各種api,然後在任務隊列中加入click,load,done等事件,當棧中的任務都執行完後就去調用任務隊列中的事件並依次執行。

 

Node

如圖(圖片是借鑑的):

3.jpg

NodeJs的運行機制:

  1. V8引擎解析js代碼
  2. 代碼中可能會調用node API,node會交給LIBUV庫處理
  3. LIBUV通過阻塞I/O和多線程實現了異步I\O
  4. 將任務的執行結果返回給V8引擎,V8引擎再將結果返回給用戶

 

Node中的Event Loop

在LIBUV內部有這樣一個事件環機制,在node啓動時會初始化事件環

nodeçEvent Loop.png

  • 這裏每一個階段都對應一個事件隊列,當Event Loop執行到某一階段的時候會將該階段對應的事件依次執行。
  • 當隊列執行完畢or執行的數量超過上限的時候,會自動轉入下一階段。 這裏我們重點關注一下poll階段

 

poll階段

pollé¶æ®µ.png

 

同步,異步 阻塞和非阻塞

  • 阻塞和非阻塞指的是調用者的狀態,關注的是程序在等待調用結果時的狀態
  • 同步和異步指的是被調用者是如何通知的,關注的是消息通知機制

 

宏任務和微任務

  • 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')
    })
})   // 如果不寫時間,默認是4ms
// 棧
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》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 會比 其他微任務、宏任務執行快

《3》i/o 文件操作(宏任務),搭配微任務,誰優先執行呢?

let fs = require('fs');
fs.readFile('./1/log',function(){
    console.log('fs')
})
process.nextTick(function(){
    console.log('text')
})
// 結果打印:text  fs

i/o 文件操作(宏任務), 如果有微任務,先執行微任務,再執行文件讀取

《4》

setTimeout(() => {
    console.log(1)
    setTimeout(() => {
        console.log(2)
    }, 500);
}, 1000);

setTimeout(() => {
    console.log(3)
    setTimeout(() => {
        console.log(4)
    }, 1000);
}, 500)

//3 1 2 4
// 個人觀點:從上往下,所以上面的定時器先註冊,時間一樣所以執行2再執行4

 

談談Event Loop(事件循環)機制

javascript運行機制:Event Loop

總是一知半解的Event Loop

Event Loop 其實也就這點事

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章