瀏覽器event loop和node的event loop講解

蘆夢宇發表於2018-05-19

瀏覽器渲染是多執行緒的,包括:

瀏覽器的渲染程式是多執行緒的包括:

1.GUI渲染執行緒(ui執行緒和js執行緒是互斥的
2.JS引擎執行緒
3.事件觸發執行緒
4.定時觸發器執行緒
5.非同步http請求執行緒
其中3,4,5的觸發過程是通過事件環來控制的

瀏覽器的event loop

任務分為同步任務和非同步任務
1.所有同步任務都在主執行緒上執行,形成一個執行棧
2.主執行緒之外,還存在一個任務佇列。只要非同步任務有了執行結果,就在任務佇列之中放置一個事件。
3.一旦執行棧中的所有同步任務執行完畢,系統就會讀取任務佇列,將佇列中的事件放到執行棧中依次執行
4.主執行緒從任務佇列中讀取事件,這個過程是迴圈不斷的
整個的這種執行機制又稱為Event Loop(事件迴圈)

瀏覽器event loop和node的event loop講解
舉個例子:

console.log(1);

setTimeout(()=>{
  console.log(2);
},1000);
setTimeout(()=>{
  console.log(3);
},500)
複製程式碼

先執行主執行緒中的程式碼,當500ms時把定時器任務放到任務佇列中,當1000ms時再把下一個定時任務放到 任務佇列中。(注意時非同步任務有了結果才放到佇列中,而不是上來就把它放到佇列裡面)。當主執行緒空閒了,就會讀取任務佇列(先進先出),將任務放到執行棧中執行,然後再去讀取任務佇列。(棧中的程式碼執行完畢後 會呼叫佇列中的程式碼,不停的迴圈)

非同步任務分為macrotask和microtask

macrotask(Task):setTimeout、setInterval、setImmediate、I/O、UI rendering
microtask(job):process.nextTick、promises、Object.observe、MutationObserver
microtask優先順序高於microtask
就是說主執行緒空閒時,系統會優先取microtask中的任務,執行完畢後才會讀取macrotask中的任務

舉例子如下:

Promise.resolve().then(function promise1 () {
       console.log('promise1');
    })
setTimeout(function setTimeout1 (){
    console.log('setTimeout1')
    Promise.resolve().then(function  promise2 () {
       console.log('promise2');
    })
}, 0)

setTimeout(function setTimeout2 (){
   console.log('setTimeout2')
}, 0)

複製程式碼

執行過程:

主線成執行過程中,遇到promise1,將其放到microtask中;遇到setTimeout1,到達觸發時機時(4ms,將其放到macroTask中;遇到setTimeout2,到達觸發時機時(4ms),將其放到macroTask中。

  1. microTask:[promise1];macroTask:[setTimeout1,setTimeout2]; 先執行promise1,microTask清空了,再讀macroTask,執行setTimeout1,隨後將promise2放入microTask
  2. microTask:[promise2];macroTask:[setTimeout2],先執行promise2,清空microTask, 在執行setTimeout2

總結: 每一次執行棧清空先清空微任務,然後從eventloop中取出巨集任務推入棧中執行作為下一個迴圈,結束後又清空微任務,直到執行棧,巨集任務全部清空

node 中的event loop

瀏覽器event loop和node的event loop講解

timers 階段: 這個階段執行setTimeout(callback) and setInterval(callback)預定的callback;
I/O callbacks 階段: 執行除了close事件的callbacks、被timers(定時器,setTimeout、setInterval等)設定的callbacks、setImmediate()設定的callbacks之外的callbacks;

idle, prepare 階段: 僅node內部使用;

poll 階段: 獲取新的I/O事件, 適當的條件下node將阻塞在這裡,執行poll中的I/O佇列,檢查定時器是否到時(到時了會跑到timer階段);

check 階段: 執行setImmediate() 設定的callbacks;
close callbacks 階段: 比如socket.on(‘close’, callback)的callback會在這個階段執行. 上面每一個階段都對應一個佇列,當event loop執行到一個指定階段時,node將執行該階段的fifo queue(佇列),當佇列callback執行完或者執行callbacks數量超過該階段的上限時,eventloop會轉入下一下階段.
注意上面六個階段都不包括 process.nextTick()

process.nextTick

process.nextTick()不在eventloop的任何階段執行,而是在各個階段切換的中間執行。 當切換階段時,並且微任務佇列中有任務,就執行微任務佇列中的任務。

fs.readFile(__filename, () => {
     setTimeout(() => {
        console.log('setTimeout');
    }, 0);
    setImmediate(() => {
        console.log('setImmediate');
        process.nextTick(()=>{
            console.log('nextTick3');
        })
    });
    
    Promise.resolve().then(()=>{
      console.log('Promise')
    })
    process.nextTick(()=>{
        console.log('nextTick2');
    })
})
複製程式碼

1.timer階段:[setTimeout] check階段:[setImmediate] 微任務[nextTick2,Promise];
1)讀檔案,頁面進入poll階段,poll切換到check階段時,執行微任務,列印nextTick2,Promise(process.nextTick優先順序高於Promise)
2)接著執行check階段,列印setImmediate
3)迴圈執行到timer階段,執行微任務nextTick3
4)執行timer,列印setTimeout
結果: nextTick2,Promise,setImmediate,nextTick3,setTimeout

poll階段

在node.js裡,任何非同步方法(除timer,close,setImmediate之外)完成時,都會將其callback加到poll queue裡,並立即執行。
poll 階段有兩個主要的功能:
1.處理poll佇列(poll quenue)的事件(callback);
2.執行timers的callback,當到達timers指定的時間時;
如果event loop進入了poll階段,且沒設定timer
1)poll任務不為空,則執行poll階段的任務,直到執行完佇列,或者達到上限,然後進入check階段等
2)poll任務為空,則繼續執行下一階段
如若even loop 進入poll階段,且設定了timer
1) poll佇列不為空,且進入空閒狀態,evenploop會檢查timers,佇列,如果有到達的任務,則迴圈進入timers階段任務,執行完畢後,再迴圈進入poll,判斷剛才的任務是否完成,完成了則執行,否則,繼續判斷是否有timers任務滿足執行條件。
2)poll佇列不為空,且在執行佇列中任務,執行完之後,如果有timers達到執行條件,則迴圈進入執行timers階段的任務

function someAsyncOperation (callback) {
  var time = Date.now();
  // 花費9毫秒
  fs.readFile('/path/to/xxxx.pdf', callback);
}

var timeoutScheduled = Date.now();
var fileReadTime = 0;
var delay = 0;

setTimeout(function () {
  delay = Date.now() - timeoutScheduled;
}, 5);

someAsyncOperation(function () {
  fileReadtime = Date.now();
  while(Date.now() - fileReadtime < 20) {

  }
  console.log('setTimeout: ' + (delay) + "ms have passed since I was scheduled");
});
複製程式碼

分析
當時程式啟動時,event loop初始化:
1.timer階段(無callback到達,setTimeout需要10毫秒)
2.i/o callback階段,無非同步i/o完成
3.忽略
4.poll階段,阻塞在這裡,當執行5ms時,poll依然空閒,但已設定timer,且時間已到達,因此,event loop需要迴圈到timer階段,執行setTimeout callback,由於從poll --> timer中間要經歷check,close階段,這些階段也會消耗一定時間,因此執行setTimeout callback實際是7毫秒 然後又回到poll階段等待非同步i/o完成,在9毫秒時fs.readFile完成,其callback加入poll queue並執行。

相關文章