javascript
Node.js event loop 和 JS 浏览器环境下的事件循环的区别
Node.js? event loop 和 JS 瀏覽器環(huán)境下的事件循環(huán)的區(qū)別:
1.線程與進(jìn)程:
JS 是單線程執(zhí)行的,指的是一個(gè)進(jìn)程里只有一個(gè)主線程,那到底什么是線程?什么是進(jìn)程?
進(jìn)程是 CPU 資源分配的最小單位;線程是 CPU 調(diào)度的最小單位。
一個(gè)進(jìn)程由一個(gè)或多個(gè)線程組成,線程是一個(gè)進(jìn)程中代碼的不同執(zhí)行路線。
一個(gè)進(jìn)程的內(nèi)存空間是共享的,每個(gè)線程都可用這些共享內(nèi)存。
?
2.多進(jìn)程和多線程
多進(jìn)程:在同一個(gè)時(shí)間里,同一個(gè)計(jì)算機(jī)系統(tǒng)中如果允許兩個(gè)或兩個(gè)以上的進(jìn)程處于運(yùn)行狀態(tài)。多進(jìn)程帶來的好處是明顯的,比如你可以聽歌的同時(shí),打開編輯器敲代碼,編輯器和聽歌軟件的進(jìn)程之間絲毫不會(huì)相互干擾。
多線程:程序中包含多個(gè)執(zhí)行流,即在一個(gè)程序中可以同時(shí)運(yùn)行多個(gè)不同的線程來執(zhí)行不同的任務(wù),也就是說允許單個(gè)程序創(chuàng)建多個(gè)并行執(zhí)行的線程來完成各自的任務(wù)。
?
以 Chrome 瀏覽器中為例,當(dāng)你打開一個(gè) Tab 頁時(shí),其實(shí)就是創(chuàng)建了一個(gè)進(jìn)程,一個(gè)進(jìn)程中可以有多個(gè)線程(下文會(huì)詳細(xì)介紹),比如渲染線程、JS 引擎線程、HTTP 請求線程等等。當(dāng)你發(fā)起一個(gè)請求時(shí),其實(shí)就是創(chuàng)建了一個(gè)線程,當(dāng)請求結(jié)束后,該線程可能就會(huì)被銷毀。
?
3.瀏覽器
瀏覽器內(nèi)核是通過取得頁面內(nèi)容、整理信息(應(yīng)用 CSS)、計(jì)算和組合最終輸出可視化的圖像結(jié)果,通常也被稱為渲染引擎。
瀏覽器內(nèi)核是多線程,在內(nèi)核控制下各線程相互配合以保持同步,一個(gè)瀏覽器通常由以下常駐線程組成:
- GUI 渲染線程
- JavaScript 引擎線程
- 定時(shí)觸發(fā)器線程
- 事件觸發(fā)線程
- 異步 http 請求線程
1. GUI 渲染線程
主要負(fù)責(zé)頁面的渲染,解析 HTML、CSS,構(gòu)建 DOM 樹,布局和繪制等。
當(dāng)界面需要重繪或者由于某種操作引發(fā)回流時(shí),將執(zhí)行該線程。
該線程與 JS 引擎線程互斥,當(dāng)執(zhí)行 JS 引擎線程時(shí),GUI 渲染會(huì)被掛起,當(dāng)任務(wù)隊(duì)列空閑時(shí),JS 引擎才會(huì)去執(zhí)行 GUI 渲染。
2. JS 引擎線程
該線程當(dāng)然是主要負(fù)責(zé)處理 JavaScript 腳本,執(zhí)行代碼。
也是主要負(fù)責(zé)執(zhí)行準(zhǔn)備好待執(zhí)行的事件,即定時(shí)器計(jì)數(shù)結(jié)束,或者異步請求成功并正確返回時(shí),將依次進(jìn)入任務(wù)隊(duì)列,等待 JS 引擎線程的執(zhí)行。
當(dāng)然,該線程與 GUI 渲染線程互斥,當(dāng) JS 引擎線程執(zhí)行 JavaScript 腳本時(shí)間過長,將導(dǎo)致頁面渲染的阻塞。
3. 定時(shí)器觸發(fā)線程
負(fù)責(zé)執(zhí)行異步定時(shí)器一類的函數(shù)的線程,如: setTimeout,setInterval。
主線程依次執(zhí)行代碼時(shí),遇到定時(shí)器,會(huì)將定時(shí)器交給該線程處理,當(dāng)計(jì)數(shù)完畢后,事件觸發(fā)線程會(huì)將計(jì)數(shù)完畢后的事件加入到任務(wù)隊(duì)列的尾部,等待 JS 引擎線程執(zhí)行。
4. 事件觸發(fā)線程
主要負(fù)責(zé)將準(zhǔn)備好的事件交給 JS 引擎線程執(zhí)行。
比如 setTimeout 定時(shí)器計(jì)數(shù)結(jié)束, ajax 等異步請求成功并觸發(fā)回調(diào)函數(shù),或者用戶觸發(fā)點(diǎn)擊事件時(shí),該線程會(huì)將整裝待發(fā)的事件依次加入到任務(wù)隊(duì)列的隊(duì)尾,等待 JS 引擎線程的執(zhí)行。
?
5. 異步 http 請求線程
負(fù)責(zé)執(zhí)行異步請求一類的函數(shù)的線程,如: Promise,axios,ajax 等。
主線程依次執(zhí)行代碼時(shí),遇到異步請求,會(huì)將函數(shù)交給該線程處理,當(dāng)監(jiān)聽到狀態(tài)碼變更,如果有回調(diào)函數(shù),事件觸發(fā)線程會(huì)將回調(diào)函數(shù)加入到任務(wù)隊(duì)列的尾部,等待 JS 引擎線程執(zhí)行。
?
window.onload = function(){console.log(1)setTimeout(function(){console.log(2)},0)for (var i = 0; i < 10; i++) {if(i == 999) console.log(10)}console.log(4) }上面代碼輸出結(jié)果為1,3,4,2
?
瀏覽器的 Event-loop:
事件循環(huán)中的異步隊(duì)列有兩種:macro(宏任務(wù))隊(duì)列和 micro(微任務(wù))隊(duì)列。宏任務(wù)隊(duì)列可以有多個(gè),微任務(wù)隊(duì)列只有一個(gè)。
常見的 macro-task 比如:setTimeout、setInterval、 setImmediate、script(整體代碼)、 I/O 操作、UI 渲染等。
常見的 micro-task 比如: process.nextTick、new Promise().then(回調(diào))、MutationObserver(html5 新特性) 等。
?
全局上下文(script 標(biāo)簽)被推入執(zhí)行棧,同步代碼執(zhí)行。在執(zhí)行的過程中,會(huì)判斷是同步任務(wù)還是異步任務(wù),通過對一些接口的調(diào)用,可以產(chǎn)生新的 macro-task 與 micro-task,它們會(huì)分別被推入各自的任務(wù)隊(duì)列里。同步代碼執(zhí)行完了,script 腳本會(huì)被移出 macro 隊(duì)列,這個(gè)過程本質(zhì)上是隊(duì)列的 macro-task 的執(zhí)行和出隊(duì)的過程。
上一步我們出隊(duì)的是一個(gè) macro-task,這一步我們處理的是 micro-task。但需要注意的是:當(dāng) macro-task 出隊(duì)時(shí),任務(wù)是一個(gè)一個(gè)執(zhí)行的;而 micro-task 出隊(duì)時(shí),任務(wù)是一隊(duì)一隊(duì)執(zhí)行的。因此,我們處理 micro 隊(duì)列這一步,會(huì)逐個(gè)執(zhí)行隊(duì)列中的任務(wù)并把它出隊(duì),直到隊(duì)列被清空。
當(dāng)某個(gè)宏任務(wù)執(zhí)行完后,會(huì)查看是否有微任務(wù)隊(duì)列。如果有,先執(zhí)行微任務(wù)隊(duì)列中的所有任務(wù),如果沒有,會(huì)讀取宏任務(wù)隊(duì)列中排在最前的任務(wù),執(zhí)行宏任務(wù)的過程中,遇到微任務(wù),依次加入微任務(wù)隊(duì)列。棧空后,再次讀取微任務(wù)隊(duì)列里的任務(wù),依次類推。
Promise.resolve().then(()=>{console.log('Promise1')setTimeout(()=>{console.log('setTimeout2')},0) }) setTimeout(()=>{console.log('setTimeout1')Promise.resolve().then(()=>{console.log('Promise2')}) },0)最后輸出結(jié)果是 Promise1,setTimeout1,Promise2,setTimeout2
1.一開始執(zhí)行棧的同步任務(wù)(這屬于宏任務(wù))執(zhí)行完畢,會(huì)去查看是否有微任務(wù)隊(duì)列,上題中存在(有且只有一個(gè)),然后執(zhí)行微任務(wù)隊(duì)列中的所有任務(wù)輸出 Promise1,同時(shí)會(huì)生成一個(gè)宏任務(wù) setTimeout2
2.然后去查看宏任務(wù)隊(duì)列,宏任務(wù) setTimeout1 在 setTimeout2 之前,先執(zhí)行宏任務(wù) setTimeout1,輸出 setTimeout1
3.在執(zhí)行宏任務(wù) setTimeout1 時(shí)會(huì)生成微任務(wù) Promise2 ,放入微任務(wù)隊(duì)列中,接著先去清空微任務(wù)隊(duì)列中的所有任務(wù),輸出 Promise2
4.清空完微任務(wù)隊(duì)列中的所有任務(wù)后,就又會(huì)去宏任務(wù)隊(duì)列取一個(gè),這回執(zhí)行的是 setTimeout2
Node 中的 Event Loop
Node 中的 Event Loop 和瀏覽器中的是完全不相同的東西。Node.js 采用 V8 作為 js 的解析引擎,而 I/O 處理方面使用了自己設(shè)計(jì)的 libuv,libuv 是一個(gè)基于事件驅(qū)動(dòng)的跨平臺(tái)抽象層,封裝了不同操作系統(tǒng)一些底層特性,對外提供統(tǒng)一的 API,事件循環(huán)機(jī)制也是它里面的實(shí)現(xiàn)(下文會(huì)詳細(xì)介紹)
?
Node.js 的運(yùn)行機(jī)制如下:
1.V8 引擎解析 JavaScript 腳本。
2.解析后的代碼,調(diào)用 Node API。
3.libuv 庫負(fù)責(zé) Node API 的執(zhí)行。它將不同的任務(wù)分配給不同的線程,形成一個(gè) Event Loop(事件循環(huán)),以異步的方式將任務(wù)的執(zhí)行結(jié)果返回給 V8 引擎。
4.V8 引擎再將結(jié)果返回給用戶。
?
六個(gè)階段
其中 libuv 引擎中的事件循環(huán)分為 6 個(gè)階段,它們會(huì)按照順序反復(fù)運(yùn)行。每當(dāng)進(jìn)入某一個(gè)階段的時(shí)候,都會(huì)從對應(yīng)的回調(diào)隊(duì)列中取出函數(shù)去執(zhí)行。當(dāng)隊(duì)列為空或者執(zhí)行的回調(diào)函數(shù)數(shù)量到達(dá)系統(tǒng)設(shè)定的閾值,就會(huì)進(jìn)入下一階段。
?
node 中的事件循環(huán)的順序:
外部輸入數(shù)據(jù)–>輪詢階段(poll)–>檢查階段(check)–>關(guān)閉事件回調(diào)階段(close callback)–>定時(shí)器檢測階段(timer)–>I/O 事件回調(diào)階段(I/O callbacks)–>閑置階段(idle, prepare)–>輪詢階段(按照該順序反復(fù)運(yùn)行)…
1.timers 階段:這個(gè)階段執(zhí)行 timer(setTimeout、setInterval)的回調(diào)
2.I/O callbacks 階段:處理一些上一輪循環(huán)中的少數(shù)未執(zhí)行的 I/O 回調(diào)
3.idle, prepare 階段:僅 node 內(nèi)部使用
4.poll 階段:獲取新的 I/O 事件, 適當(dāng)?shù)臈l件下 node 將阻塞在這里
5.check 階段:執(zhí)行 setImmediate() 的回調(diào)
6.close callbacks 階段:執(zhí)行 socket 的 close 事件回調(diào)
上面六個(gè)階段都不包括 process.nextTick()
?
(1) timer
timers 階段會(huì)執(zhí)行 setTimeout 和 setInterval 回調(diào),并且是由 poll 階段控制的。
同樣,在 Node 中定時(shí)器指定的時(shí)間也不是準(zhǔn)確時(shí)間,只能是盡快執(zhí)行。
(2) poll
poll 是一個(gè)至關(guān)重要的階段,這一階段中,系統(tǒng)會(huì)做兩件事情
回到 timer 階段執(zhí)行回調(diào)
執(zhí)行 I/O 回調(diào)
并且在進(jìn)入該階段時(shí)如果沒有設(shè)定了 timer 的話,會(huì)發(fā)生以下兩件事情
如果 poll 隊(duì)列不為空,會(huì)遍歷回調(diào)隊(duì)列并同步執(zhí)行,直到隊(duì)列為空或者達(dá)到系統(tǒng)限制
如果 poll 隊(duì)列為空時(shí),會(huì)有兩件事發(fā)生
如果有 setImmediate 回調(diào)需要執(zhí)行,poll 階段會(huì)停止并且進(jìn)入到 check 階段執(zhí)行回調(diào)
如果沒有 setImmediate 回調(diào)需要執(zhí)行,會(huì)等待回調(diào)被加入到隊(duì)列中并立即執(zhí)行回調(diào),這里同樣會(huì)有個(gè)超時(shí)時(shí)間設(shè)置防止一直等待下去
當(dāng)然設(shè)定了 timer 的話且 poll 隊(duì)列為空,則會(huì)判斷是否有 timer 超時(shí),如果有的話會(huì)回到 timer 階段執(zhí)行回調(diào)。
(3) check 階段
setImmediate()的回調(diào)會(huì)被加入 check 隊(duì)列中,從 event loop 的階段圖可以知道,check 階段的執(zhí)行順序在 poll 階段之后。
console.log('start') setTimeout(() => {console.log('timer1')Promise.resolve().then(function() {console.log('promise1')}) }, 0) setTimeout(() => {console.log('timer2')Promise.resolve().then(function() {console.log('promise2')}) }, 0) Promise.resolve().then(function() {console.log('promise3') }) console.log('end') //start=>end=>promise3=>timer1=>timer2=>promise1=>promise2 ---------------------
一開始執(zhí)行棧的同步任務(wù)(這屬于宏任務(wù))執(zhí)行完畢后(依次打印出 start end,并將 2 個(gè) timer 依次放入 timer 隊(duì)列),會(huì)先去執(zhí)行微任務(wù)(這點(diǎn)跟瀏覽器端的一樣),所以打印出 promise3
然后進(jìn)入 timers 階段,執(zhí)行 timer1 的回調(diào)函數(shù),打印 timer1,并將 promise.then 回調(diào)放入 microtask 隊(duì)列,同樣的步驟執(zhí)行 timer2,打印 timer2;這點(diǎn)跟瀏覽器端相差比較大,timers 階段有幾個(gè) setTimeout/setInterval 都會(huì)依次執(zhí)行,并不像瀏覽器端,每執(zhí)行一個(gè)宏任務(wù)后就去執(zhí)行一個(gè)微任務(wù)(關(guān)于 Node 與瀏覽器的 Event Loop 差異,下文還會(huì)詳細(xì)介紹)。
?
process.nextTick
這個(gè)函數(shù)其實(shí)是獨(dú)立于 Event Loop 之外的,它有一個(gè)自己的隊(duì)列,當(dāng)每個(gè)階段完成后,如果存在 nextTick 隊(duì)列,就會(huì)清空隊(duì)列中的所有回調(diào)函數(shù),并且優(yōu)先于其他 microtask 執(zhí)行。
setTimeout(() => {console.log('timer1')Promise.resolve().then(function() {console.log('promise1')}) }, 0) process.nextTick(() => {console.log('nextTick')process.nextTick(() => {console.log('nextTick')process.nextTick(() => {console.log('nextTick')process.nextTick(() => {console.log('nextTick')})})}) }) // nextTick=>nextTick=>nextTick=>nextTick=>timer1=>promise1
Node 與瀏覽器的 Event Loop 差異
瀏覽器環(huán)境下,microtask 的任務(wù)隊(duì)列是每個(gè) macrotask 執(zhí)行完之后執(zhí)行。而在 Node.js 中,microtask 會(huì)在事件循環(huán)的各個(gè)階段之間執(zhí)行,也就是一個(gè)階段執(zhí)行完畢,就會(huì)去執(zhí)行 microtask 隊(duì)列的任務(wù)。
?
?
參考鏈接:
https://blog.csdn.net/Fundebug/article/details/86487117
https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick/
https://www.jianshu.com/p/b221e6e36dcb
轉(zhuǎn)載于:https://www.cnblogs.com/winyh/p/11144618.html
總結(jié)
以上是生活随笔為你收集整理的Node.js event loop 和 JS 浏览器环境下的事件循环的区别的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 易经读书笔记11地天泰
- 下一篇: Spring Boot 注解配置文件自动