前言
學(xué)習(xí)真的是一個很奇妙的過程。
本意是學(xué)習(xí)狀態(tài)管理工具Redux,其中涉及到Promise異步編程知識,發(fā)現(xiàn)不太熟悉,于是決定先學(xué)學(xué)Promise相關(guān)知識。
Promise文檔中,提及了傳統(tǒng)的異步編程解決方案,感覺略顯模糊,那就先回顧下傳統(tǒng)的異步編程解決方案吧。
傳統(tǒng)異步編程解決方案中涉及了定時器,好像沒研究過定時器的原理,一不做,二不休,決定先把定時器的原理搞清楚再回去學(xué)習(xí)上面的知識。
然鵝,還沒結(jié)束,定時器的原理建立在js引擎線程上?。?!
所以,這篇文章,記錄的是瀏覽器的線程和進程相關(guān)知識。[捂臉/]

瀏覽器線程與進程
線程和進程的概念此處不再贅述,直擊主題-瀏覽器的線程與進程。
以谷歌瀏覽器為例,打開瀏覽器,然后shift+Esc或者(Chrome的更多工具 -> 任務(wù)管理器),打開chrome的任務(wù)管理器:

1、瀏覽器進程(Browser進程)
瀏覽器的主進程,負責協(xié)調(diào)、主控,只有一個(無論打開幾個tab或幾個彈窗),主要作用:
- 負責瀏覽器界面顯示,與用戶交互,如前進,后退等;
- 負責各個頁面的管理,創(chuàng)建和銷毀其他進程;
- 將Renderer進程得到的內(nèi)存中的Bitmap,繪制到用戶界面上;
- 網(wǎng)絡(luò)資源的管理,下載等;
2、CPU進程
用于3D繪制等,可禁用掉,只有一個。
3、第三方插件進程
每種類型的插件對應(yīng)一個進程,僅當使用該插件時才創(chuàng)建。
4、瀏覽器渲染進程
瀏覽器渲染進程(Renderer進程),即通常所說的瀏覽器內(nèi)核,主要作用:頁面渲染、腳本執(zhí)行、事件處理等。每一個標簽頁的打開都會創(chuàng)建一個Renderer進程,且互不影響。默認一個標簽頁一個Renderer進程,但是,有時候瀏覽器會將多個進程合并(暫時沒查到合并的依據(jù)),比如打開多個空白標簽頁:

瀏覽器為什么是多進程?
對于簡單的網(wǎng)頁,一個進程處理多個網(wǎng)頁是可行的。
但是把很多復(fù)雜的網(wǎng)頁放進一個進程,瀏覽器面臨在健壯性,響應(yīng)速度,安全性方面的挑戰(zhàn)。如果一個tab頁崩潰,將導(dǎo)致同進程的其他頁面崩潰,極其影響用戶體驗。
另外進程之間是不共享資源和地址空間的,所以不會存在太多的安全問題。
當然,多進程相對于單進程,內(nèi)存等資源消耗更大,有點空間換時間的意思。這大概也是瀏覽器中存在多個tab頁共用一個進程的情況的原因吧。
瀏覽器渲染進程(Renderer進程,瀏覽器內(nèi)核)
作為前端開發(fā),最關(guān)心的自然是頁面渲染、腳本執(zhí)行、事件處理等過程,這就不得不介紹Renderer進程。
首先,Renderer進程包含以下5種線程:
1、GUI渲染線程
- 負責渲染瀏覽器界面,解析HTML,CSS,構(gòu)建DOM樹和RenderObject樹,布局和繪制等;
- 當界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時,該線程就會執(zhí)行;
- 注意:GUI渲染線程與JS引擎線程是互斥的,當JS引擎執(zhí)行時GUI線程會被掛起(相當于被凍結(jié)了),GUI更新會被保存在一個隊列中等到JS引擎空閑時立即被執(zhí)行。
2、JS引擎線程
- JS引擎線程也稱為JS內(nèi)核,負責處理Javascript腳本程序,解析Javascript腳本,運行代碼;
- JS引擎一直等待著任務(wù)隊列中任務(wù)的到來,然后加以處理,一個Tab頁(renderer進程)中無論什么時候都只有一個JS線程在運行JS程序;
- 注意:GUI渲染線程與JS引擎線程的互斥關(guān)系,所以如果JS執(zhí)行的時間過長,會造成頁面的渲染不連貫,導(dǎo)致頁面渲染加載阻塞。
3、事件觸發(fā)線程
- 歸屬于瀏覽器而不是JS引擎,用來控制事件循環(huán)(可以理解,JS引擎自己都忙不過來,需要瀏覽器另開線程協(xié)助);
- 當JS引擎執(zhí)行代碼塊如setTimeOut時(也可是來自瀏覽器內(nèi)核的其他線程,如鼠標點擊、AJAX異步請求等),會將對應(yīng)任務(wù)添加到事件觸發(fā)線程中;
- 當對應(yīng)的事件符合觸發(fā)條件被觸發(fā)時,該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理;
- 注意:由于JS的單線程關(guān)系,所以這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閑時才會去執(zhí)行);
4、定時器觸發(fā)線程
- 即setInterval與setTimeout所在線程;
- 瀏覽器定時計數(shù)器并不是由JS引擎計數(shù)的,因為JS引擎是單線程的, 如果處于阻塞線程狀態(tài)就會影響記計時的準確性;
- 因此使用單獨線程來計時并觸發(fā)定時器,計時完畢后,添加到事件隊列中,等待JS引擎空閑后執(zhí)行,所以定時器中的任務(wù)在設(shè)定的時間點不一定能夠準時執(zhí)行,定時器只是在指定時間點將任務(wù)添加到事件隊列中;
- 注意:W3C在HTML標準中規(guī)定,定時器的定時時間不能小于4ms,如果是小于4ms,則默認為4ms。
5、異步http請求線程
- XMLHttpRequest連接后通過瀏覽器新開一個線程請求;
-
檢測到狀態(tài)變更時,如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件,將回調(diào)函數(shù)放入事件隊列中,等待JS引擎空閑后執(zhí)行;
綜上所述,瀏覽器各進程線程關(guān)系大致如下圖:
瀏覽器進程和線程關(guān)系圖
JS為什么是單線程?
JavaScript的主要用途主要是用戶互動,和操作DOM。線程之間資源共享,相互影響,如果JavaScript同時有兩個線程,一個線程在某個DOM節(jié)點上添加內(nèi)容,另一個線程刪除了這個節(jié)點,這時這兩個節(jié)點會有很大沖突,為了避免這個沖突,所以決定了它只能是單線程,否則會帶來很復(fù)雜的同步問題。此外HTML5提出Web Worker標準,允許JavaScript腳本創(chuàng)建多個線程(GUI線程, 異步HTTP請求線程, 定時觸發(fā)器線程...),但是子線程完全受主線程控制,這個新標準并沒有改變JavaScript單線程的本質(zhì)。
JS運行機制
接下來分析分析JS引擎的運行機制:
- 主線程(我理解為JS引擎線程)運行時,形成一個執(zhí)行棧;
- 事件觸發(fā)線程管理著一個任務(wù)隊列,執(zhí)行棧中的代碼調(diào)用某些api時,它們會在任務(wù)隊列中添加各種事件(當滿足觸發(fā)條件后,如ajax請求完畢,定時器到點時);
-
執(zhí)行棧中任務(wù)執(zhí)行完畢(JS引擎線程空閑),系統(tǒng)讀取任務(wù)隊列,將任務(wù)添加到執(zhí)行棧中,開始執(zhí)行;瀏覽器線程合作
圖片較大,原圖訪問:瀏覽器線程合作
JS運行機制進階(宏任務(wù)/微任務(wù))
上面的JS運行機制在ES5中完全夠用了,但是對于現(xiàn)在的ES6,還需要對JS運行機制做進一步探究,發(fā)現(xiàn)一篇文章寫得很好很詳細,我就直接貼鏈接了,有興趣的童鞋請移步:前端基礎(chǔ)進階(十二):深入核心,詳解事件循環(huán)機制
到此,基本上把瀏覽器的線程進程問題理清了。
本文借鑒了一些大神的博文,加之我自己的理解,有敘述或理解不準確之處,歡迎指正,感謝!
參考文章鏈接:
前端基礎(chǔ)進階(十二):深入核心,詳解事件循環(huán)機制
瀏覽器的運行機制—2.瀏覽器都包含哪些進程?
瀏覽器的多線程與js引擎的單線程
從瀏覽器多進程到JS單線程,JS運行機制最全面的一次梳理

