摘要:引擎線程,也稱為內(nèi)核,負(fù)責(zé)處理腳本程序,例如引擎。異步請(qǐng)求線程,也就是發(fā)出請(qǐng)求后,接收響應(yīng)檢測(cè)狀態(tài)變更等都是這個(gè)線程管理的。為了解決這個(gè)問(wèn)題,提出標(biāo)準(zhǔn),允許腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制,且不得操作。
本文主要參閱了以下兩篇文章,對(duì)JS的Event Loop運(yùn)行機(jī)制基礎(chǔ)知識(shí)進(jìn)行了整理。
從瀏覽器多進(jìn)程到JS單線程,JS運(yùn)行機(jī)制最全面的一次梳理
JavaScript 運(yùn)行機(jī)制詳解:再談Event Loop
大家都知道JavaScript是單線程的,這就引申出一個(gè)問(wèn)題,進(jìn)程與線程是什么,他們的區(qū)別是什么?
先給出進(jìn)程和線程的定義:
進(jìn)程是cpu資源分配的最小單位(是能擁有資源和獨(dú)立運(yùn)行的最小單位)
線程是cpu調(diào)度的最小單位(線程是建立在進(jìn)程的基礎(chǔ)上的一次程序運(yùn)行單位,一個(gè)進(jìn)程中可以有多個(gè)線程)
用工廠和工人的例子來(lái)形象闡述:
- 進(jìn)程是一個(gè)工廠,工廠有它的獨(dú)立資源 -> 系統(tǒng)分配的內(nèi)存(獨(dú)立的一塊內(nèi)存) - 工廠之間相互獨(dú)立 -> 進(jìn)程之間相互獨(dú)立 - 線程是工廠中的工人,多個(gè)工人協(xié)作完成任務(wù) -> 多個(gè)線程在進(jìn)程中協(xié)作完成任務(wù) - 工廠內(nèi)有一個(gè)或多個(gè)工人 -> 一個(gè)進(jìn)程由一個(gè)或多個(gè)線程組成 - 工人之間共享工廠的資源 -> 同一進(jìn)程下的各個(gè)線程之間共享進(jìn)程的內(nèi)存空間(包括代碼段、數(shù)據(jù)集、堆等)
補(bǔ)充:
我們所說(shuō)的單線程和多線程,是指一個(gè)進(jìn)程內(nèi)是單一線程還是多線程。
進(jìn)程間的通信方式包括: 管道pipe、 命名管道FIFO、消息隊(duì)列MessageQueue、共享存儲(chǔ)SharedMemory、信號(hào)量Semaphore、套接字Socket、信號(hào)。
瀏覽器是多進(jìn)程的關(guān)于瀏覽器進(jìn)程問(wèn)題可以簡(jiǎn)單基礎(chǔ)三點(diǎn):
瀏覽器是多進(jìn)程的。
瀏覽器之所以能夠運(yùn)行,是因?yàn)橄到y(tǒng)給它的進(jìn)程分配了資源(cpu、內(nèi)存)。
簡(jiǎn)單點(diǎn)理解,每打開(kāi)一個(gè)Tab頁(yè),就相當(dāng)于創(chuàng)建了一個(gè)獨(dú)立的瀏覽器進(jìn)程。
平時(shí) coding 接觸到最多的一個(gè)瀏覽器進(jìn)程是瀏覽器渲染進(jìn)程(瀏覽器內(nèi)核),它管理著頁(yè)面渲染。腳本執(zhí)行,事件處理等。要同時(shí)處理這么多事情,渲染進(jìn)程顯然是多線程的,它主要包括以下5個(gè)常駐線程:
GUI渲染線程,負(fù)責(zé)渲染瀏覽器界面,解析HTML,CSS,構(gòu)建DOM樹(shù)和RenderObject樹(shù),布局和繪制等。
JS引擎線程,也稱為JS內(nèi)核,負(fù)責(zé)處理Javascript腳本程序,(例如V8引擎)。
事件觸發(fā)線程,用來(lái)控制事件循環(huán)(可以理解為,JS引擎線程自己都忙不過(guò)來(lái),需要瀏覽器另開(kāi)線程協(xié)助)。
定時(shí)觸發(fā)器線程,瀏覽器定時(shí)計(jì)數(shù)器并不是由JavaScript引擎計(jì)數(shù)的,(因?yàn)镴avaScript引擎是單線程的, 如果處于阻塞線程狀態(tài)就會(huì)影響記計(jì)時(shí)的準(zhǔn)確),JS中常用的setInterval和setTimeout就歸這個(gè)線程管理。
異步http請(qǐng)求線程,也就是ajax發(fā)出http請(qǐng)求后,接收響應(yīng)、檢測(cè)狀態(tài)變更等都是這個(gè)線程管理的。
我們常說(shuō)的JavaScript是單線程的,其實(shí)就是說(shuō)的JS引擎是單線程的,它僅僅是瀏覽器渲染進(jìn)程種的一個(gè)線程。為什么呢?因?yàn)镴avaScript的主要作用是與用戶互動(dòng),以及操作DOM,如果JavaScript有兩個(gè)線程,一個(gè)線程對(duì)一個(gè)DOM節(jié)點(diǎn)執(zhí)行 A 操作,另一個(gè)線程這個(gè)DOM節(jié)點(diǎn)執(zhí)行 B 操作,那么就會(huì)起沖突,所以JavaScript在前端的應(yīng)用就注定了它是單線程的。
然而JavaScript的單線程特性就注定我們不用它去完成密集的 cpu 運(yùn)算,因?yàn)槊芗?cpu 運(yùn)算耗時(shí)過(guò)長(zhǎng),阻塞頁(yè)面渲染。為了解決這個(gè)問(wèn)題,HTML5提出 Web Worker 標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制,且不得操作DOM。
Event LoopJavaScript 是單線程的帶來(lái)的問(wèn)題是:所有任務(wù)都必須同步執(zhí)行,問(wèn)題就出現(xiàn)了,很多 I/O 過(guò)程是非常耗時(shí)的(如http 請(qǐng)求數(shù)據(jù)),如果要等到 I/O 過(guò)程結(jié)束再執(zhí)行后續(xù)任務(wù),就會(huì)出現(xiàn)頁(yè)面的卡頓、cpu 的閑置。于是異步的任務(wù)就出現(xiàn)了,異步任務(wù)是指掛起處于等待中的任務(wù),繼續(xù)執(zhí)行同步任務(wù),等到結(jié)果返回再去繼續(xù)執(zhí)行被掛起的任務(wù)。于是,JavaScript 的任務(wù)可以分為同步任務(wù)和異步任務(wù)。下面就引出 Event Loop 機(jī)制:
所有同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧
主線程之外,事件觸發(fā)線程管理著一個(gè)任務(wù)隊(duì)列,只要異步任務(wù)有了運(yùn)行結(jié)果,就在任務(wù)隊(duì)列之中放置一個(gè)事件。
一旦執(zhí)行棧中的所有同步任務(wù)執(zhí)行完畢(此時(shí)JS引擎空閑),系統(tǒng)就會(huì)讀取任務(wù)隊(duì)列,將可運(yùn)行的異步任務(wù)添加到執(zhí)行棧中,開(kāi)始執(zhí)行。
如上圖所示,執(zhí)行棧中的代碼會(huì)調(diào)用一個(gè)異步的API,它們會(huì)在任務(wù)隊(duì)列中添加各種事件(或者說(shuō)回調(diào)函數(shù)),另外用戶的操作如click、mousedown等都會(huì)在任務(wù)隊(duì)列中添加事件。只要執(zhí)行棧中的代碼執(zhí)行完畢,主線程就會(huì)去讀取任務(wù)隊(duì)列,將可執(zhí)行的回調(diào)函數(shù)放到執(zhí)行棧中執(zhí)行。
總結(jié)一下:
執(zhí)行棧執(zhí)行完畢 -> 主線程讀取任務(wù)隊(duì)列,并執(zhí)行回調(diào)函數(shù) -> 執(zhí)行棧執(zhí)行完畢 -> 主線程讀取任務(wù)隊(duì)列,并執(zhí)行回調(diào)函數(shù) ...
這個(gè)過(guò)程一直循環(huán)下去,所以就叫事件循環(huán)(Event Loop)。
setTimeout 和 setInterval前面提到了瀏覽器的定時(shí)觸發(fā)器線程,它的主要作用就是計(jì)時(shí),setTimeout 和 setInterval 就由它來(lái)控制,原理就是到達(dá)設(shè)置時(shí)間后,往任務(wù)隊(duì)列中添加這兩個(gè)函數(shù)中指定的回調(diào)函數(shù)。
setTimeout() 方法用于在指定的毫秒數(shù)后調(diào)用函數(shù)或計(jì)算表達(dá)式。但是需要注意的是,實(shí)際是計(jì)時(shí)結(jié)束后定時(shí)觸發(fā)器線程才會(huì)將回調(diào)函數(shù)放到任務(wù)隊(duì)列中去,此時(shí)任務(wù)隊(duì)列中這個(gè)回調(diào)之前可能已經(jīng)有一些事件待處理,并且一定要執(zhí)行棧的任務(wù)執(zhí)行完后才會(huì)開(kāi)始執(zhí)行任務(wù)隊(duì)列中的任務(wù),所以 setTimeout() 中回調(diào)開(kāi)始執(zhí)行的時(shí)間是:執(zhí)行棧執(zhí)行時(shí)間 + 任務(wù)隊(duì)列前方回調(diào)執(zhí)行時(shí)間 + 延遲時(shí)間
setInterval() 方法可按照指定的時(shí)間間隔來(lái)周期性調(diào)用函數(shù)或計(jì)算表達(dá)式。它的問(wèn)題在于:每次都精確的隔一段時(shí)間將一個(gè)回調(diào)放到任務(wù)隊(duì)列中,并沒(méi)有考慮到內(nèi)部回調(diào)函數(shù)執(zhí)行所需時(shí)間,這就會(huì)導(dǎo)致兩種問(wèn)題:
回調(diào)函數(shù)執(zhí)行需要時(shí)間,兩個(gè)函數(shù)執(zhí)行的時(shí)間間隔會(huì)小于設(shè)定值;
如果回調(diào)函數(shù)執(zhí)行時(shí)間大于設(shè)定間隔,就會(huì)出現(xiàn)上一個(gè)加入任務(wù)隊(duì)列中的回調(diào)還沒(méi)執(zhí)行完,下一個(gè)回調(diào)就被加入任務(wù)隊(duì)列了,就會(huì)出現(xiàn)累計(jì)效應(yīng),即后面的回調(diào)會(huì)連續(xù)執(zhí)行。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/96459.html
摘要:規(guī)范中定義了瀏覽器何時(shí)進(jìn)行渲染更新,了解它有助于性能優(yōu)化。結(jié)合一些資料,對(duì)上邊規(guī)范給出一些理解有誤請(qǐng)指正每個(gè)線程都有自己的。列為,列為,列為。我們都知道是單線程,渲染計(jì)算和腳本運(yùn)行共用同一線程網(wǎng)絡(luò)請(qǐng)求會(huì)有其他線程,導(dǎo)致腳本運(yùn)行會(huì)阻塞渲染。 本文轉(zhuǎn)自blog 轉(zhuǎn)載請(qǐng)注明出處 異步的思考 event loops隱藏得比較深,很多人對(duì)它很陌生。但提起異步,相信每個(gè)人都知道。異步背后的靠山就是...
摘要:但是導(dǎo)致了很明顯的性能問(wèn)題。上述兩個(gè)例子其實(shí)是在這個(gè)中找到的,第一個(gè)使用的版本是,這個(gè)版本的實(shí)現(xiàn)是采用了,而后因?yàn)榈睦锏挠?,于是尤雨溪更改了?shí)現(xiàn),換成了,也就是后一個(gè)所使用的。后來(lái)尤雨溪了解到是將回調(diào)放入的隊(duì)列。 結(jié)論 對(duì)于event loop 可以抽象成一段簡(jiǎn)單的代碼表示 for (macroTask of macroTaskQueue) { // 1. Handle cur...
摘要:但是導(dǎo)致了很明顯的性能問(wèn)題。上述兩個(gè)例子其實(shí)是在這個(gè)中找到的,第一個(gè)使用的版本是,這個(gè)版本的實(shí)現(xiàn)是采用了,而后因?yàn)榈睦锏挠?,于是尤雨溪更改了?shí)現(xiàn),換成了,也就是后一個(gè)所使用的。后來(lái)尤雨溪了解到是將回調(diào)放入的隊(duì)列。 結(jié)論 對(duì)于event loop 可以抽象成一段簡(jiǎn)單的代碼表示 for (macroTask of macroTaskQueue) { // 1. Handle cur...
摘要:事件循環(huán)了解了在引擎中是如何工作了之后,來(lái)看下如何使用異步回調(diào)函數(shù)來(lái)避免代碼。從回調(diào)函數(shù)被放入后秒鐘,把移到中。由于事件循環(huán)持續(xù)地監(jiān)測(cè)調(diào)用棧是否已空,此時(shí)它一注意到調(diào)用??樟耍驼{(diào)用并創(chuàng)建一個(gè)新的調(diào)用棧。 聽(tīng)多了JavaScript單線程,異步,V8,便會(huì)很想去知道JavaScript是如何利用單線程來(lái)實(shí)現(xiàn)所謂的異步的。我參考了一些文章,了解到一個(gè)很重要的詞匯:事件循環(huán)(Event L...
摘要:主線程在任務(wù)隊(duì)列中讀取事件,這個(gè)過(guò)程是循環(huán)不斷地,所以這種運(yùn)行機(jī)制叫做事件循環(huán)是在執(zhí)行棧同步代碼結(jié)束之后,下一次任務(wù)隊(duì)列執(zhí)行之前。 單線程 javascript為什么是單線程語(yǔ)言,原因在于如果是多線程,當(dāng)一個(gè)線程對(duì)DOM節(jié)點(diǎn)做添加內(nèi)容操作的時(shí)候,另一個(gè)線程要?jiǎng)h除這個(gè)DOM節(jié)點(diǎn),這個(gè)時(shí)候,瀏覽器應(yīng)該怎么選擇,這就造成了混亂,為了解決這類問(wèn)題,在一開(kāi)始的時(shí)候,javascript就采用單線...
閱讀 2444·2021-11-25 09:43
閱讀 1228·2021-09-07 10:16
閱讀 2651·2021-08-20 09:38
閱讀 2965·2019-08-30 15:55
閱讀 1510·2019-08-30 13:21
閱讀 914·2019-08-29 15:37
閱讀 1470·2019-08-27 10:56
閱讀 2112·2019-08-26 13:45