久久精品五月,日韩不卡视频在线观看,国产精品videossex久久发布 ,久久av综合

站長資訊網
最全最豐富的資訊網站

一文詳解Node.js中的事件循環

本篇文章帶大家深度理解Node中的事件循環,希望對大家有所幫助!

一文詳解Node.js中的事件循環

node.js極速入門課程:進入學習

ALL THE TIME,我們寫的的大部分javascript代碼都是在瀏覽器環境下編譯運行的,因此可能我們對瀏覽器的事件循環機制了解比Node.JS的事件循環更深入一些,但是最近寫開始深入NodeJS學習的時候,發現NodeJS的事件循環機制和瀏覽器端有很大的區別,特此記錄來深入的學習了下,以幫助自己及小伙伴們忘記后查閱及理解。

一文詳解Node.js中的事件循環

什么是事件循環

首先我們需要了解一下最基礎的一些東西,比如這個事件循環,事件循環是指Node.js執行非阻塞I/O操作,盡管==JavaScript是單線程的==,但由于大多數==內核都是多線程==的,Node.js會盡可能將操作裝載到系統內核。因此它們可以處理在后臺執行的多個操作。當其中一個操作完成時,內核會告訴Node.js,以便Node.js可以將相應的回調添加到輪詢隊列中以最終執行。【相關教程推薦:nodejs視頻教程】

當Node.js啟動時會初始化event loop, 每一個event loop都會包含按如下順序六個循環階段:

   ┌───────────────────────┐ ┌─>│        timers         │ │  └──────────┬────────────┘ │  ┌──────────┴────────────┐ │  │     I/O callbacks     │ │  └──────────┬────────────┘ │  ┌──────────┴────────────┐ │  │     idle, prepare     │ │  └──────────┬────────────┘      ┌───────────────┐ │  ┌──────────┴────────────┐      │   incoming:   │ │  │         poll          │<─────┤  connections, │ │  └──────────┬────────────┘      │   data, etc.  │ │  ┌──────────┴────────────┐      └───────────────┘ │  │        check          │ │  └──────────┬────────────┘ │  ┌──────────┴────────────┐ └──┤    close callbacks    │    └───────────────────────┘
登錄后復制

  • 1. timers 階段: 這個階段執行 setTimeout(callback)setInterval(callback) 預定的 callback;
  • 2. I/O callbacks 階段: 此階段執行某些系統操作的回調,例如TCP錯誤的類型。 例如,如果TCP套接字在嘗試連接時收到 ECONNREFUSED,則某些* nix系統希望等待報告錯誤。 這將操作將等待在==I/O回調階段==執行;
  • 3. idle, prepare 階段: 僅node內部使用;
  • 4. poll 階段: 獲取新的I/O事件, 例如操作讀取文件等等,適當的條件下node將阻塞在這里;
  • 5. check 階段: 執行 setImmediate() 設定的callbacks;
  • 6. close callbacks 階段: 比如 socket.on(‘close’, callback) 的callback會在這個階段執行;

事件循環詳解

一文詳解Node.js中的事件循環

這個圖是整個 Node.js 的運行原理,從左到右,從上到下,Node.js 被分為了四層,分別是 應用層V8引擎層、Node API層LIBUV層

  • 應用層: 即 JavaScript 交互層,常見的就是 Node.js 的模塊,比如 http,fs
  • V8引擎層: 即利用 V8 引擎來解析JavaScript 語法,進而和下層 API 交互
  • NodeAPI層: 為上層模塊提供系統調用,一般是由 C 語言來實現,和操作系統進行交互 。
  • LIBUV層: 是跨平臺的底層封裝,實現了 事件循環、文件操作等,是 Node.js 實現異步的核心 。

每個循環階段內容詳解

timers階段 一個timer指定一個下限時間而不是準確時間,在達到這個下限時間后執行回調。在指定時間過后,timers會盡可能早地執行回調,但系統調度或者其它回調的執行可能會延遲它們。

  • 注意:技術上來說,poll 階段控制 timers 什么時候執行。

  • 注意:這個下限時間有個范圍:[1, 2147483647],如果設定的時間不在這個范圍,將被設置為1。

I/O callbacks階段 這個階段執行一些系統操作的回調。比如TCP錯誤,如一個TCP socket在想要連接時收到ECONNREFUSED, 類unix系統會等待以報告錯誤,這就會放到 I/O callbacks 階段的隊列執行. 名字會讓人誤解為執行I/O回調處理程序, 實際上I/O回調會由poll階段處理.

poll階段 poll 階段有兩個主要功能:(1)執行下限時間已經達到的timers的回調,(2)然后處理 poll 隊列里的事件。 當event loop進入 poll 階段,并且 沒有設定的 timers(there are no timers scheduled),會發生下面兩件事之一:

  • 如果 poll 隊列不空,event loop會遍歷隊列并同步執行回調,直到隊列清空或執行的回調數到達系統上限;

  • 如果 poll 隊列為空,則發生以下兩件事之一:

    • 如果代碼已經被setImmediate()設定了回調, event loop將結束 poll 階段進入 check 階段來執行 check 隊列(里面的回調 callback)。
    • 如果代碼沒有被setImmediate()設定回調,event loop將阻塞在該階段等待回調被加入 poll 隊列,并立即執行。
  • 但是,當event loop進入 poll 階段,并且 有設定的timers,一旦 poll 隊列為空(poll 階段空閑狀態): event loop將檢查timers,如果有1個或多個timers的下限時間已經到達,event loop將繞回 timers 階段,并執行 timer 隊列。

check階段 這個階段允許在 poll 階段結束后立即執行回調。如果 poll 階段空閑,并且有被setImmediate()設定的回調,event loop會轉到 check 階段而不是繼續等待。

  • setImmediate() 實際上是一個特殊的timer,跑在event loop中一個獨立的階段。它使用libuv的API 來設定在 poll 階段結束后立即執行回調。

  • 通常上來講,隨著代碼執行,event loop終將進入 poll 階段,在這個階段等待 incoming connection, request 等等。但是,只要有被setImmediate()設定了回調,一旦 poll 階段空閑,那么程序將結束 poll 階段并進入 check 階段,而不是繼續等待 poll 事件們 (poll events)。

close callbacks 階段 如果一個 socket 或 handle 被突然關掉(比如 socket.destroy()),close事件將在這個階段被觸發,否則將通過process.nextTick()觸發

這里呢,我們通過偽代碼來說明一下,這個流程:

// 事件循環本身相當于一個死循環,當代碼開始執行的時候,事件循環就已經啟動了 // 然后順序調用不同階段的方法 while(true){ // timer階段     timer() // I/O callbacks階段     IO() // idle階段     IDLE() // poll階段     poll() // check階段     check() // close階段     close() } // 在一次循環中,當事件循環進入到某一階段,加入進入到check階段,突然timer階段的事件就緒,也會等到當前這次循環結束,再去執行對應的timer階段的回調函數  // 下面看這里例子 const fs = require('fs')  // timers階段 const startTime = Date.now(); setTimeout(() => {     const endTime = Date.now()     console.log(`timers: ${endTime - startTime}`) }, 1000)  // poll階段(等待新的事件出現) const readFileStart =  Date.now(); fs.readFile('./Demo.txt', (err, data) => {     if (err) throw err     let endTime = Date.now()     // 獲取文件讀取的時間     console.log(`read time: ${endTime - readFileStart}`)     // 通過while循環將fs回調強制阻塞5000s     while(endTime - readFileStart < 5000){         endTime = Date.now()     }  })   // check階段 setImmediate(() => {     console.log('check階段') }) /*控制臺打印check階段read time: 9timers: 5008通過上述結果進行分析,1.代碼執行到定時器setTimeOut,目前timers階段對應的事件列表為空,在1000s后才會放入事件2.事件循環進入到poll階段,開始不斷的輪詢監聽事件3.fs模塊異步執行,根據文件大小,可能執行時間長短不同,這里我使用的小文件,事件大概在9s左右4.setImmediate執行,poll階段暫時未監測到事件,發現有setImmediate函數,跳轉到check階段執行check階段事件(打印check階段),第一次時間循環結束,開始下一輪事件循環5.因為時間仍未到定時器截止時間,所以事件循環有一次進入到poll階段,進行輪詢6.讀取文件完畢,fs產生了一個事件進入到poll階段的事件隊列,此時事件隊列準備執行callback,所以會打印(read time: 9),人工阻塞了5s,雖然此時timer定時器事件已經被添加,但是因為這一階段的事件循環為完成,所以不會被執行,(如果這里是死循環,那么定時器代碼永遠無法執行)7.fs回調阻塞5s后,當前事件循環結束,進入到下一輪事件循環,發現timer事件隊列有事件,所以開始執行 打印timers: 5008ps:1.將定時器延遲時間改為5ms的時候,小于文件讀取時間,那么就會先監聽到timers階段有事件進入,從而進入到timers階段執行,執行完畢繼續進行事件循環check階段timers: 6read time: 50082.將定時器事件設置為0ms,會在進入到poll階段的時候發現timers階段已經有callback,那么會直接執行,然后執行完畢在下一階段循環,執行check階段,poll隊列的回調函數timers: 2check階段read time: 7 */
登錄后復制

走進案例解析

我們來看一個簡單的EventLoop的例子:

const fs = require('fs'); let counts = 0;  // 定義一個 wait 方法 function wait (mstime) {   let date = Date.now();   while (Date.now() - date < mstime) {     // do nothing   } }  // 讀取本地文件 操作IO function asyncOperation (callback) {   fs.readFile(__dirname + '/' + __filename, callback); }  const lastTime = Date.now();  // setTimeout setTimeout(() => {   console.log('timers', Date.now() - lastTime + 'ms'); }, 0);  // process.nextTick process.nextTick(() => {   // 進入event loop   // timers階段之前執行   wait(20);   asyncOperation(() => {     console.log('poll');   });   });  /** * timers 21ms * poll */
登錄后復制

這里呢,為了讓這個setTimeout優先于fs.readFile 回調, 執行了process.nextTick, 表示在進入timers階段前, 等待20ms后執行文件讀取.

1. nextTicksetImmediate

  • process.nextTick 不屬于事件循環的任何一個階段,它屬于該階段與下階段之間的過渡, 即本階段執行結束, 進入下一個階段前, 所要執行的回調。有給人一種插隊的感覺.

  • setImmediate 的回調處于check階段, 當poll階段的隊列為空, 且check階段的事件隊列存在的時候,切換到check階段執行,參考nodejs進階視頻講解:進入學習

nextTick 遞歸的危害

由于nextTick具有插隊的機制,nextTick的遞歸會讓事件循環機制無法進入下一個階段. 導致I/O處理完成或者定時任務超時后仍然無法執行, 導致了其它事件處理程序處于饑餓狀態. 為了防止遞歸產生的問題, Node.js 提供了一個 process.maxTickDepth (默認 1000)。

const fs = require('fs'); let counts = 0;  function wait (mstime) {   let date = Date.now();   while (Date.now() - date < mstime) {     // do nothing   } }  function nextTick () {   process.nextTick(() => {     wait(20);     console.log('nextTick');     nextTick();   }); }  const lastTime = Date.now();  setTimeout(() => {   console.log('timers', Date.now() - lastTime + 'ms'); }, 0);  nextTick();
登錄后復制

此時永遠無法跳到timer階段去執行setTimeout里面的回調方法, 因為在進入timers階段前有不斷的nextTick插入執行. 除非執行了1000次到了執行上限,所以上面這個案例會不斷地打印出nextTick字符串

2. setImmediate

如果在一個I/O周期內進行調度,setImmediate() 將始終在任何定時器(setTimeout、setInterval)之前執行.

3. setTimeoutsetImmediate

  • setImmediate()被設計在 poll 階段結束后立即執行回調;
  • setTimeout()被設計在指定下限時間到達后執行回調;

無 I/O 處理情況下:

setTimeout(function timeout () {   console.log('timeout'); },0);  setImmediate(function immediate () {   console.log('immediate'); });
登錄后復制

執行結果:

C:Users92809Desktopnode_test>node test.js timeout immediate  C:Users92809Desktopnode_test>node test.js timeout immediate  C:Users92809Desktopnode_test>node test.js timeout immediate  C:Users92809Desktopnode_test>node test.js immediate timeout
登錄后復制

從結果,我們可以發現,這里打印輸出出來的結果,并沒有什么固定的先后順序,偏向于隨機,為什么會發生這樣的情況呢?

答:首先進入的是timers階段,如果我們的機器性能一般,那么進入timers階段,1ms已經過去了 ==(setTimeout(fn, 0)等價于setTimeout(fn, 1))==,那么setTimeout的回調會首先執行。

如果沒有到1ms,那么在timers階段的時候,下限時間沒到,setTimeout回調不執行,事件循環來到了poll階段,這個時候隊列為空,于是往下繼續,先執行了setImmediate()的回調函數,之后在下一個事件循環再執行setTimemout的回調函數。

問題總結:而我們在==執行啟動代碼==的時候,進入timers的時間延遲其實是==隨機的==,并不是確定的,所以會出現兩個函數執行順序隨機的情況。

那我們再來看一段代碼:

var fs = require('fs')  fs.readFile(__filename, () => {     setTimeout(() => {         console.log('timeout');     }, 0);     setImmediate(() => {         console.log('immediate');     }); });
登錄后復制

打印結果如下:

C:Users92809Desktopnode_test>node test.js immediate timeout  C:Users92809Desktopnode_test>node test.js immediate timeout  C:Users92809Desktopnode_test>node test.js immediate timeout  # ... 省略 n 多次使用 node test.js 命令 ,結果都輸出 immediate timeout
登錄后復制

這里,為啥和上面的隨機timer不一致呢,我們來分析下原因:

原因如下:fs.readFile的回調是在poll階段執行的,當其回調執行完畢之后,poll隊列為空,而setTimeout入了timers的隊列,此時有代碼 setImmediate(),于是事件循環先進入check階段執行回調,之后在下一個事件循環再在timers階段中執行回調。

當然,下面的小案例同理:

setTimeout(() => {     setImmediate(() => {         console.log('setImmediate');     });     setTimeout(() => {         console.log('setTimeout');     }, 0); }, 0);
登錄后復制

以上的代碼在timers階段執行外部的setTimeout回調后,內層的setTimeoutsetImmediate入隊,之后事件循環繼續往后面的階段走,走到poll階段的時候發現隊列為空,此時有代碼有setImmedate(),所以直接進入check階段執行響應回調(==注意這里沒有去檢測timers隊列中是否有成員到達下限事件,因為setImmediate()優先==)。之后在第二個事件循環的timers階段中再去執行相應的回調。

綜上所演示,我們可以總結如下:

  • 如果兩者都在主模塊中調用,那么執行先后取決于進程性能,也就是你的電腦好撇,當然也就是隨機。
  • 如果兩者都不在主模塊調用(被一個異步操作包裹),那么**setImmediate的回調永遠先執行**。

4. nextTickPromise

概念:對于這兩個,我們可以把它們理解成一個微任務。也就是說,它其實不屬于事件循環的一部分。 那么他們是在什么時候執行呢? 不管在什么地方調用,他們都會在其所處的事件循環最后,事件循環進入下一個循環的階段前執行。

setTimeout(() => {     console.log('timeout0');     new Promise((resolve, reject) => { resolve('resolved') }).then(res => console.log(res));     new Promise((resolve, reject) => {       setTimeout(()=>{         resolve('timeout resolved')       })     }).then(res => console.log(res));     process.nextTick(() => {         console.log('nextTick1');         process.nextTick(() => {             console.log('nextTick2');         });     });     process.nextTick(() => {         console.log('nextTick3');     });     console.log('sync');     setTimeout(() => {         console.log('timeout2');     }, 0); }, 0);
登錄后復制

控制臺打印如下:

C:Users92809Desktopnode_test>node test.js timeout0 sync nextTick1 nextTick3 nextTick2 resolved timeout2 timeout resolved
登錄后復制

最總結:timers階段執行外層setTimeout的回調,遇到同步代碼先執行,也就有timeout0、sync的輸出。遇到process.nextTickPromise后入微任務隊列,依次nextTick1nextTick3nextTick2resolved入隊后出隊輸出。之后,在下一個事件循環的timers階段,執行setTimeout回調輸出timeout2以及微任務Promise里面的setTimeout,輸出timeout resolved。(這里要說明的是 微任務nextTick優先級要比Promise要高)

5. 最后案例

代碼片段1:

setImmediate(function(){   console.log("setImmediate");   setImmediate(function(){     console.log("嵌套setImmediate");   });   process.nextTick(function(){     console.log("nextTick");   }) });  /*     C:Users92809Desktopnode_test>node test.js    setImmediate    nextTick    嵌套setImmediate*/
登錄后復制

解析:

事件循環check階段執行回調函數輸出setImmediate,之后輸出nextTick。嵌套的setImmediate在下一個事件循環的check階段執行回調輸出嵌套的setImmediate。

代碼片段2:

async function async1(){     console.log('async1 start')     await async2()     console.log('async1 end')   } async function async2(){     console.log('async2') } console.log('script start') setTimeout(function(){     console.log('setTimeout0')  },0)   setTimeout(function(){     console.log('setTimeout3')  },3)   setImmediate(() => console.log('setImmediate')); process.nextTick(() => console.log('nextTick')); async1(); new Promise(function(resolve){     console.log('promise1')     resolve();     console.log('promise2') }).then(function(){     console.log('promise3') }) console.log('script end')
登錄后復制

打印結果為:

C:Users92809Desktopnode_test>node test.js script start async1 start async2 promise1 promise2 script end nextTick promise3 async1 end setTimeout0 setTimeout3 setImmediate
登錄后復制

大家呢,可以先看著代碼,默默地在心底走一變代碼,然后對比輸出的結果,當然最后三位,我個人認為是有點問題的,畢竟在主模塊運行,大家的答案,最后三位可能會有偏差;

贊(0)
分享到: 更多 (0)
?
網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
久久精品五月,日韩不卡视频在线观看,国产精品videossex久久发布 ,久久av综合
日韩视频不卡| av高清一区| 成人福利视频| 久久成人高清| 国产乱论精品| 久久国产精品免费精品3p| 鲁大师成人一区二区三区| 亚洲韩日在线| 久久精品123| 香蕉久久99| www在线观看黄色| 欧美日韩免费看片| 香蕉视频亚洲一级| 色婷婷精品视频| 久久久人人人| 欧美女激情福利| 亚洲自拍另类| 在线免费观看亚洲| 亚洲不卡视频| 日韩精彩视频在线观看| 日韩在线一区二区| 免播放器亚洲一区| 久色成人在线| 久久av一区| 蜜桃一区二区三区在线观看| 国产精品av一区二区| 亚洲高清成人| 国产亚洲亚洲| 亚洲免费激情| 日韩国产精品久久久久久亚洲| 国产精品一二| 国产高清不卡| 国产一区导航| 日韩激情综合| 你懂的亚洲视频| 国产精品99一区二区三| 久久一区二区三区喷水| 亚洲一区二区三区高清不卡| 亚洲精品自拍| 福利一区二区| 精品亚洲美女网站| 蜜臀av一区二区在线免费观看| 日韩欧美2区| 国产精品日韩精品在线播放| 成人午夜亚洲| 激情欧美一区二区三区| 日韩在线播放一区二区| 麻豆视频久久| 久久夜夜操妹子| 九九久久婷婷| 中文字幕成人| 精品高清久久| 亚洲激情偷拍| 国产精品一区二区美女视频免费看 | 精品国产乱码久久久久久樱花| 日韩a一区二区| 蜜臀久久99精品久久久久宅男| 国产日产一区| 久久国产小视频| 亚洲乱码久久| 97视频热人人精品免费| 日韩专区欧美专区| 欧美xxxx中国| 亚洲女同中文字幕| 国产精品多人| 亚洲永久字幕| 成人午夜网址| 激情91久久| 国产精品手机在线播放| 亚洲天堂1区| 涩涩涩久久久成人精品| 精品国产日韩欧美精品国产欧美日韩一区二区三区 | 国产二区精品| 亚洲免费成人av在线| 国产成人久久| 男女男精品网站| 精品国产亚洲一区二区三区| 久久国产福利| 97国产精品| 日韩av在线播放中文字幕| 久久久久久免费视频| 91精品在线免费视频| 久久精品国产99久久| 国产剧情一区二区在线观看| 国产乱码午夜在线视频| 亚洲青青久久| 久久69成人| 欧美特黄一区| 国产欧美日韩一区二区三区在线| 国产在线|日韩| 久久99久久久精品欧美| 麻豆成人在线| 欧美色图一区| 在线天堂资源www在线污| 国产精品中文字幕亚洲欧美| 午夜在线精品偷拍| 久久精品免费一区二区三区| 国产一区二区三区日韩精品| 亚洲一区二区三区四区五区午夜 | 婷婷激情图片久久| 精品美女在线视频| 国产乱论精品| 91亚洲无吗| 男女激情视频一区| 国产偷自视频区视频一区二区| 亚洲天堂1区| 日韩理论视频| 激情综合五月| 久久中文字幕一区二区三区| 日韩高清三区| 在线精品国产亚洲| 国产精品色网| 五月天久久网站| 波多野结衣久久精品| 国产精品伦一区二区| 日韩精品导航| 一本一本久久| 亚洲午夜精品久久久久久app| 岛国av在线网站| 国产麻豆一区二区三区| 日本亚洲不卡| 中文不卡在线| 亚洲一区日韩在线| 亚洲欧美日韩国产一区| 9色精品在线| 久久国产66| 久久xxxx| 亚洲精品国产精品粉嫩| 亚州国产精品| 久久黄色影视| 欧美日韩伊人| 欧美国产中文高清| 久久久91麻豆精品国产一区| 精品国产不卡| 欧美黄色网页| 欧美成人久久| 视频一区欧美日韩| 青青国产91久久久久久| 国产欧美日韩| 色一区二区三区四区| 久久亚洲成人| 蜜乳av另类精品一区二区| 免费视频最近日韩| 日韩精品一区二区三区免费视频 | 国产日韩中文在线中文字幕| 国产高清视频一区二区| 都市激情国产精品| 在线国产一区二区| 黄色在线一区| 日韩av黄色在线| 免费一区二区三区在线视频| 成人亚洲一区二区| 久久精品影视| 亚洲综合婷婷| 欧美日韩一区自拍| 国产精品网址| 久久久久.com| 亚洲综合福利| 国产精品久久| 91综合网人人| 亚洲精品1区2区| 91亚洲精品在看在线观看高清| 欧美黑人巨大videos精品| 久久久777| 日本精品国产| 欧洲在线一区| 天堂俺去俺来也www久久婷婷| 久久爱www.| 婷婷中文字幕一区| 国产另类在线| 高清av不卡| 日本综合精品一区| 久久久噜噜噜| 国产精品v日韩精品v欧美精品网站| 午夜精品网站| 高清一区二区三区| 国产乱码精品一区二区三区四区 | 国产精品日本欧美一区二区三区| 97精品中文字幕| 国产精品日韩精品中文字幕| 蜜臀av一区二区三区| 91成人超碰| 婷婷激情一区| 黄色aa久久| 国产成人精品一区二区三区在线| 青草综合视频| 日本精品另类| 老司机精品久久| 亚洲激情中文| 黄色欧美在线| 国产剧情在线观看一区| 免费在线观看视频一区| 亚洲黄色在线| 欧美日韩四区| 亚洲经典在线| 91久久亚洲| 亚洲一区二区动漫| 玖玖玖国产精品| 亚洲图片久久| 综合激情视频|