日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

event loop、进程和线程、任务队列

發(fā)布時(shí)間:2025/3/20 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 event loop、进程和线程、任务队列 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文原鏈接:https://cloud.tencent.com/developer/article/1106531

https://cloud.tencent.com/developer/article/1372207

  • JavaScript是單線程
  • 執(zhí)行棧、任務(wù)隊(duì)列
  • 同步任務(wù)、異步任務(wù)、宏任務(wù)、微任務(wù)
  • setTimeout()、setInterval()
  • Promise
  • process.nextTick
  • setImmediate
  • 優(yōu)先級(jí)
  • 代碼解析
  • 參考資料

先看段代碼:

console.log(1); setTimeout(function () { console.log(2); new Promise(function (resolve, reject) { console.log(3); resolve(); console.log(4); }).then(function () { console.log(5); }); }); function fn() { console.log(6); setTimeout(function () { console.log(7); }, 50); } new Promise(function (resolve, reject) { console.log(8); resolve(); console.log(9); }).then(function () { console.log(10); }); fn(); console.log(11); // 以下代碼需要在 node 環(huán)境中執(zhí)行 process.nextTick(function () { console.log(12); }); setImmediate(function () { console.log(13); });

思考一下,能給出準(zhǔn)確的輸出順序嗎?

下面我們一個(gè)一個(gè)的來(lái)了解 Event Loop 相關(guān)的知識(shí)點(diǎn),最后再一步一步分析出本段代碼最后的輸出順序。

JavaScript是單線程

首先我們先了解下進(jìn)程和線程的概念和關(guān)系:

  • 進(jìn)程:?運(yùn)行的程序就是一個(gè)進(jìn)程,比如你正在運(yùn)行的瀏覽器,它會(huì)有一個(gè)進(jìn)程。
  • 線程:?程序中獨(dú)立運(yùn)行的代碼段。一個(gè)進(jìn)程?由單個(gè)或多個(gè)?線程?組成,線程是負(fù)責(zé)執(zhí)行代碼的。

我們都知道 JavaScript 是單線程的,那么既然有單線程就有多線程,首先看看單線程與多線程的區(qū)別:

  • 單線程:?從頭執(zhí)行到尾,一行一行執(zhí)行,如果其中一行代碼報(bào)錯(cuò),那么剩下代碼將不再執(zhí)行。同時(shí)容易代碼阻塞。
  • 多線程:?代碼運(yùn)行的環(huán)境不同,各線程獨(dú)立,互不影響,避免阻塞。

那為什么JavaScript是單線程的呢?

JavaScript 的單線程,與它的用途有關(guān)。作為瀏覽器腳本語(yǔ)言,JavaScript 的主要用途是與用戶互動(dòng),以及操作 DOM。這決定了它只能是單線程,否則會(huì)帶來(lái)很復(fù)雜的同步問(wèn)題。比如,假定JavaScript同時(shí)有兩個(gè)線程,一個(gè)線程在某個(gè) DOM 節(jié)點(diǎn)上添加內(nèi)容,另一個(gè)線程刪除了這個(gè)節(jié)點(diǎn),這時(shí)瀏覽器應(yīng)該以哪個(gè)線程為準(zhǔn)呢?

所以,為了避免復(fù)雜性,從一誕生,JavaScript 就是單線程,這已經(jīng)成了這門語(yǔ)言的核心特征,將來(lái)也不會(huì)改變。

為了利用多核 CPU 的計(jì)算能力,HTML5 提出 Web Worker 標(biāo)準(zhǔn),允許 JavaScript 腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制,且不得操作 DOM。所以,這個(gè)新標(biāo)準(zhǔn)并沒(méi)有改變 JavaScript 單線程的本質(zhì)。

執(zhí)行棧、任務(wù)隊(duì)列

上圖中,主線程運(yùn)行的時(shí)候,產(chǎn)生堆(heap)和棧(stack),棧中的代碼調(diào)用各種外部API,它們?cè)?#34;任務(wù)隊(duì)列"中加入各種事件(DOM Event,ajax,setTimeout…)。只要棧中的代碼執(zhí)行完畢,主線程就會(huì)去讀取任務(wù)隊(duì)列,依次執(zhí)行那些事件所對(duì)應(yīng)的回調(diào)函數(shù)。

堆(heap):

對(duì)象被分配在一個(gè)堆中,即用以表示一個(gè)大部分非結(jié)構(gòu)化的內(nèi)存區(qū)域。

執(zhí)行棧(stack):

運(yùn)行同步代碼。執(zhí)行棧中的代碼(同步任務(wù)),總是在讀取"任務(wù)隊(duì)列"(異步任務(wù))之前執(zhí)行。

任務(wù)隊(duì)列(callback queue):

"任務(wù)隊(duì)列"是一個(gè)事件的隊(duì)列(也可以理解成消息的隊(duì)列),IO設(shè)備完成一項(xiàng)任務(wù),就在"任務(wù)隊(duì)列"中添加一個(gè)事件,表示相關(guān)的異步任務(wù)可以進(jìn)入"執(zhí)行棧"了。主線程讀取"任務(wù)隊(duì)列",就是讀取里面有哪些事件。

"任務(wù)隊(duì)列"中的事件,除了IO設(shè)備的事件以外,還包括一些用戶產(chǎn)生的事件(比如鼠標(biāo)點(diǎn)擊、頁(yè)面滾動(dòng)等等)。只要指定過(guò)回調(diào)函數(shù),這些事件發(fā)生時(shí)就會(huì)進(jìn)入"任務(wù)隊(duì)列",等待主線程讀取。

所謂"回調(diào)函數(shù)"(callback),就是那些會(huì)被主線程掛起來(lái)的代碼。異步任務(wù)必須指定回調(diào)函數(shù),當(dāng)主線程開(kāi)始執(zhí)行異步任務(wù),就是執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)。

"任務(wù)隊(duì)列"是一個(gè)先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),排在前面的事件,優(yōu)先被主線程讀取。主線程的讀取過(guò)程基本上是自動(dòng)的,只要執(zhí)行棧一清空,"任務(wù)隊(duì)列"上第一位的事件就自動(dòng)進(jìn)入主線程。但是,由于存在后文提到的"定時(shí)器"功能,主線程首先要檢查一下執(zhí)行時(shí)間,某些事件只有到了規(guī)定的時(shí)間,才能返回主線程。

同步任務(wù)、異步任務(wù)、宏任務(wù)、微任務(wù)

單線程就意味著,所有任務(wù)需要排隊(duì),前一個(gè)任務(wù)結(jié)束,才會(huì)執(zhí)行后一個(gè)任務(wù)。如果前一個(gè)任務(wù)耗時(shí)很長(zhǎng),后一個(gè)任務(wù)就不得不一直等著。

如果排隊(duì)是因?yàn)橛?jì)算量大,CPU忙不過(guò)來(lái),倒也算了,但是很多時(shí)候CPU是閑著的,因?yàn)镮O設(shè)備(輸入輸出設(shè)備)很慢(比如Ajax操作從網(wǎng)絡(luò)讀取數(shù)據(jù)),不得不等著結(jié)果出來(lái),再往下執(zhí)行。

JavaScript語(yǔ)言的設(shè)計(jì)者意識(shí)到,這時(shí)主線程完全可以不管IO設(shè)備,掛起處于等待中的任務(wù),先運(yùn)行排在后面的任務(wù)。等到IO設(shè)備返回了結(jié)果,再回過(guò)頭,把掛起的任務(wù)繼續(xù)執(zhí)行下去。

于是,廣義上將 JavaScript 所有任務(wù)可以分成兩種,一種是同步任務(wù)(synchronous),另一種是異步任務(wù)(asynchronous)。同步任務(wù)指的是,在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù);異步任務(wù)指的是,不進(jìn)入主線程、而進(jìn)入"任務(wù)隊(duì)列"(task queue)的任務(wù),只有"任務(wù)隊(duì)列"通知主線程,某個(gè)異步任務(wù)可以執(zhí)行了,該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行。

具體來(lái)說(shuō),異步執(zhí)行的運(yùn)行機(jī)制如下(同步執(zhí)行也是如此,因?yàn)樗梢员灰暈闆](méi)有異步任務(wù)的異步執(zhí)行):

1)所有同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)"執(zhí)行棧"(execution context stack); (2)主線程之外,還存在一個(gè)"任務(wù)隊(duì)列"(task queue)。只要異步任務(wù)有了運(yùn)行結(jié)果,就在"任務(wù)隊(duì)列"之中放置一個(gè)事件; (3)一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)取出"任務(wù)隊(duì)列"中事件所對(duì)應(yīng)的回調(diào)函數(shù)進(jìn)入"執(zhí)行棧",開(kāi)始執(zhí)行; (4)主線程不斷重復(fù)上面的第三步。

除了廣義上的定義,我們可以將任務(wù)進(jìn)行更精細(xì)的定義,分為宏任務(wù)與微任務(wù):

  • 宏任務(wù)(macro-task):?包括整體代碼script,setTimeout,setInterval,ajax,dom操作
  • 微任務(wù)(micro-task):?Promise

具體來(lái)說(shuō),宏任務(wù)與微任務(wù)執(zhí)行的運(yùn)行機(jī)制如下:

1)首先,將"執(zhí)行棧"最開(kāi)始的所有同步代碼(宏任務(wù))執(zhí)行完成; (2)檢查是否有微任務(wù),如有則執(zhí)行所有的微任務(wù); (3)取出"任務(wù)隊(duì)列"中事件所對(duì)應(yīng)的回調(diào)函數(shù)(宏任務(wù))進(jìn)入"執(zhí)行棧"并執(zhí)行完成; (4)再檢查是否有微任務(wù),如有則執(zhí)行所有的微任務(wù); (5)主線程不斷重復(fù)上面的(3)(4)步。

以上兩種運(yùn)行機(jī)制,主線程都從"任務(wù)隊(duì)列"中讀取事件,這個(gè)過(guò)程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱為?Event Loop(事件循環(huán))

setTimeout()、setInterval()

setTimeout() 和 setInterval() 這兩個(gè)函數(shù),它們的內(nèi)部運(yùn)行機(jī)制完全一樣,區(qū)別在于前者指定的代碼是一次性執(zhí)行,后者則為反復(fù)執(zhí)行。

setTimeout() 和 setInterval() 產(chǎn)生的任務(wù)是?異步任務(wù),也屬于?宏任務(wù)

setTimeout() 接受兩個(gè)參數(shù),第一個(gè)是回調(diào)函數(shù),第二個(gè)是推遲執(zhí)行的毫秒數(shù)。setInterval() 接受兩個(gè)參數(shù),第一個(gè)是回調(diào)函數(shù),第二個(gè)是反復(fù)執(zhí)行的毫秒數(shù)。

如果將第二個(gè)參數(shù)設(shè)置為0或者不設(shè)置,意思?并不是立即執(zhí)行,而是指定某個(gè)任務(wù)在主線程最早可得的空閑時(shí)間執(zhí)行,也就是說(shuō),盡可能早得執(zhí)行。它在"任務(wù)隊(duì)列"的尾部添加一個(gè)事件,因此要等到同步任務(wù)和"任務(wù)隊(duì)列"現(xiàn)有的事件都處理完,才會(huì)得到執(zhí)行。

所以說(shuō),setTimeout() 和 setInterval() 第二個(gè)參數(shù)設(shè)置的時(shí)間并不是絕對(duì)的,它需要根據(jù)當(dāng)前代碼最終執(zhí)行的時(shí)間來(lái)確定的,簡(jiǎn)單來(lái)說(shuō),如果當(dāng)前代碼執(zhí)行的時(shí)間(如執(zhí)行200ms)超出了推遲執(zhí)行(setTimeout(fn, 100))或反復(fù)執(zhí)行的時(shí)間(setInterval(fn, 100)),那么setTimeout(fn, 100) 和 setTimeout(fn, 0) 也就沒(méi)有區(qū)別了,setInterval(fn, 100) 和 setInterval(fn, 0) 也就沒(méi)有區(qū)別了。

Promise

Promise 相對(duì)來(lái)說(shuō)就比較特殊了,在 new Promise() 中傳入的回調(diào)函數(shù)是會(huì)?立即執(zhí)行?的,但是它的?then()?方法是在?執(zhí)行棧之后,任務(wù)隊(duì)列之前?執(zhí)行的,它屬于?微任務(wù)

process.nextTick

process.nextTick 是 Node.js 提供的一個(gè)與"任務(wù)隊(duì)列"有關(guān)的方法,它產(chǎn)生的任務(wù)是放在?執(zhí)行棧的尾部,并不屬于?宏任務(wù)?和?微任務(wù),因此它的任務(wù)?總是發(fā)生在所有異步任務(wù)之前。

setImmediate

setImmediate 是 Node.js 提供的另一個(gè)與"任務(wù)隊(duì)列"有關(guān)的方法,它產(chǎn)生的任務(wù)追加到"任務(wù)隊(duì)列"的尾部,它和?setTimeout(fn, 0)?很像,但優(yōu)先級(jí)都是 setTimeout 優(yōu)先于 setImmediate。

有時(shí)候,setTimeout 的執(zhí)行順序會(huì)在 setImmediate 的前面,有時(shí)候會(huì)在 setImmediate 的后面,這并不是 node.js 的 bug,這是因?yàn)殡m然 setTimeout 第二個(gè)參數(shù)設(shè)置為0或者不設(shè)置,但是 setTimeout 源碼中,會(huì)指定一個(gè)具體的毫秒數(shù)(node為1ms,瀏覽器為4ms),而由于當(dāng)前代碼執(zhí)行時(shí)間受到執(zhí)行環(huán)境的影響,執(zhí)行時(shí)間有所起伏,如果當(dāng)前執(zhí)行的代碼小于這個(gè)指定的值時(shí),setTimeout 還沒(méi)到推遲執(zhí)行的時(shí)間,自然就先執(zhí)行 setImmediate 了,如果當(dāng)前執(zhí)行的代碼超過(guò)這個(gè)指定的值時(shí),setTimeout 就會(huì)先于 setImmediate 執(zhí)行。

優(yōu)先級(jí)

通過(guò)上面的介紹,我們就可以得出一個(gè)代碼執(zhí)行的優(yōu)先級(jí):

同步代碼(宏任務(wù)) > process.nextTick > Promise(微任務(wù))> setTimeout(fn)、setInterval(fn)(宏任務(wù))> setImmediate(宏任務(wù))> setTimeout(fn, time)、setInterval(fn, time),其中time>0

代碼解析

回到開(kāi)頭給出的代碼,我們來(lái)一步一步解析:

運(yùn)行"執(zhí)行棧"中的代碼:

console.log(1); // setTimeout(function () { // 作為宏任務(wù),暫不執(zhí)行,放到任務(wù)隊(duì)列中(1) // console.log(2); // // new Promise(function (resolve, reject) { // console.log(3); // resolve(); // console.log(4); // }).then(function () { // console.log(5); // }); // }); function fn() { console.log(6); //setTimeout(function () { // 作為宏任務(wù),暫不執(zhí)行,放到任務(wù)隊(duì)列中(3) // console.log(7); //}, 50); } new Promise(function (resolve, reject) { console.log(8); resolve(); console.log(9); }) // .then(function () { // 作為微任務(wù),暫不執(zhí)行 // console.log(10); // }); fn(); console.log(11); process.nextTick(function () { console.log(12); }); // setImmediate(function () { // 作為宏任務(wù),暫不執(zhí)行,放到任務(wù)隊(duì)列中(2) // console.log(13); // });

此時(shí)輸出為:1 8 9 6 11 12

運(yùn)行微任務(wù):

new Promise(function (resolve, reject) { // console.log(8); // 已執(zhí)行 // resolve(); // 已執(zhí)行 // console.log(9); // 已執(zhí)行 }) .then(function () { console.log(10); });

此時(shí)輸出為:10

讀取"任務(wù)隊(duì)列"的回調(diào)函數(shù)到"執(zhí)行棧"

setTimeout(function () { console.log(2); new Promise(function (resolve, reject) { console.log(3); resolve(); console.log(4); }) //.then(function () { // 作為微任務(wù),暫不執(zhí)行 // console.log(5); //}); });

此時(shí)輸出為:2 3 4

再運(yùn)行微任務(wù):

setTimeout(function () { // console.log(2); // 已執(zhí)行 new Promise(function (resolve, reject) { // console.log(3); // 已執(zhí)行 // resolve(); // 已執(zhí)行 // console.log(4); // 已執(zhí)行 }) .then(function () { console.log(5); }); });

此時(shí)輸出為:5

再讀取"任務(wù)隊(duì)列"的回調(diào)函數(shù)到"執(zhí)行棧"

setImmediate(function () { console.log(13); });

此時(shí)輸出為:13

運(yùn)行微任務(wù):

無(wú)

再讀取"任務(wù)隊(duì)列"的回調(diào)函數(shù)到"執(zhí)行棧"

// function fn() { // 已執(zhí)行// console.log(6); // 已執(zhí)行setTimeout(function () { console.log(7); }, 50); // }

此時(shí)輸出為:7

運(yùn)行微任務(wù):

無(wú)

綜上,最終的輸出順序是:1 8 9 6 11 12 10 2 3 4 5 13 7

?

轉(zhuǎn)載于:https://www.cnblogs.com/leftJS/p/11070104.html

總結(jié)

以上是生活随笔為你收集整理的event loop、进程和线程、任务队列的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。