瀏覽器進(jìn)程/線程模型
瀏覽器進(jìn)程架構(gòu)
瀏覽器如何由進(jìn)程和線程構(gòu)建? 方案可以是單進(jìn)程多線程或多個進(jìn)程(通過IPC傳遞消息)。

這兩種架構(gòu)只是具體實(shí)現(xiàn),并無對錯,本文只討論chrome最新的架構(gòu),示意圖如下。

最上面的browser process與其他進(jìn)程合作,其他進(jìn)程處理不同的事項(xiàng)。Renderer process對應(yīng)每一個tab。直到最近,Chrome會為每個tab分配進(jìn)程,現(xiàn)在它會嘗試給每個站點(diǎn)分配進(jìn)程,包括iframe。
瀏覽器中的進(jìn)程

Browser process控制瀏覽器的chrome部分(這里的chrome指外殼,不是Chrome瀏覽器)。包括地址欄,書簽,回退/前進(jìn)按鈕
Renderer process控制每個tab內(nèi)部的展示
Plugin process控制瀏覽器點(diǎn)插件
GPU process處理其他進(jìn)程的GPU任務(wù)。之所以被拆分為單獨(dú)的GPU進(jìn)程是因?yàn)镚PU會處理多個應(yīng)用的請求,繪制在同一個表面。
另外還有Extension進(jìn)程,utility進(jìn)程等,具體可以從瀏覽器的更多工具-任務(wù)管理器中查看。
多進(jìn)程架構(gòu)的優(yōu)勢
獨(dú)立性,當(dāng)有多個tab時,有一個tab不響應(yīng),不會導(dǎo)致其他tab不響應(yīng)。

另外時安全和沙盒。操作系統(tǒng)提供了限制進(jìn)程權(quán)限的方式,瀏覽器可以使用沙盒機(jī)制限制進(jìn)程對某些功能的權(quán)限。比如Chrome瀏覽器限制renderer進(jìn)程對文件訪問的權(quán)限。
因?yàn)槊總€進(jìn)程有自己私有的內(nèi)存空間,它們通常會包含多個公共模塊的多個拷貝,比如Chrome瀏覽器的V8。這意味著更多的內(nèi)存占用,因?yàn)檫M(jìn)程無法像線程那樣共享內(nèi)存。為了節(jié)約內(nèi)存,Chrome設(shè)置了進(jìn)程的最大數(shù)量,具體數(shù)值取決于具體的硬件,但是當(dāng)達(dá)到該限制后,Chrome會將同一個站點(diǎn)的多個tab合并為一個進(jìn)程。
節(jié)約更多內(nèi)存-服務(wù)化
上面講了renderer進(jìn)程合并以節(jié)約內(nèi)存的做法,同樣的方法也適用于browser進(jìn)程。Chrome正通過架構(gòu)變化將browser應(yīng)用的各個部分當(dāng)做服務(wù)運(yùn)行,以便于更方便的拆分和融合。
基本思路是在強(qiáng)勁的硬件上,將多個服務(wù)拆成不同的進(jìn)程,相反,將多個服務(wù)融合為一個進(jìn)程。在Android中就有所體現(xiàn)。


比如上面一種是將網(wǎng)絡(luò)/UI/存儲/設(shè)備等服務(wù)用一個Browser進(jìn)程,另一種是每個服務(wù)一個進(jìn)程。
Site isolation
站點(diǎn)隔離針對的是有iframe的情況,Chrome會分配一個單獨(dú)的renderer進(jìn)程給iframe。前面說過每個tab一個進(jìn)程的模型,它允許跨站的iframe和網(wǎng)頁在一個進(jìn)程中,同源策略是web主要的安全模型,它保證不經(jīng)允許無法跨站訪問數(shù)據(jù)。而進(jìn)程隔離是最有效的方式。這也是一項(xiàng)大工程,它改變了iframe和其它的交流機(jī)制,哪怕是簡單的使用Ctrl+F搜索也意味著需要檢索不同的進(jìn)程。這也是為什么瀏覽器工程師將site isolation作為一個重要里程碑。
navigation進(jìn)程間的合作
來看一個簡單的案例:在瀏覽器里輸入URL,瀏覽器從互聯(lián)網(wǎng)獲取數(shù)據(jù),展示頁面。這里聚焦在用戶請求站點(diǎn),瀏覽器準(zhǔn)備渲染頁面這個過程-也叫作navigation。
如前所述,在tab外的都有browser進(jìn)程控制,browser進(jìn)程里有繪制按鈕和輸入框的UI線程,處理網(wǎng)絡(luò)任務(wù)的網(wǎng)絡(luò)線程,存儲線程。當(dāng)在地址欄輸入一個URL,輸入由browser進(jìn)程的UI線程處理。

Navigation
當(dāng)用戶在地址欄輸入,UI線程需要解析決定是關(guān)鍵詞還是網(wǎng)站地址,從而是導(dǎo)向搜索引擎還是某個網(wǎng)站。
當(dāng)用戶輸入url回車后,UI線程發(fā)起網(wǎng)絡(luò)請求獲取站點(diǎn)內(nèi)容。tab角落顯示加載提示的同時,網(wǎng)絡(luò)線程通過必要的網(wǎng)絡(luò)協(xié)議(DNS,TLS)請求內(nèi)容。如果收到的是重定向比如301,會發(fā)起另一個URL的請求。
網(wǎng)絡(luò)線程收到響應(yīng)后,通過檢查內(nèi)容確定數(shù)據(jù)類型(MIME Type sniffing)。如果確定是HTML文件后,將數(shù)據(jù)傳遞給renderer進(jìn)程,如果是壓縮文件或其它文件,意味著需要將數(shù)據(jù)傳給下載管理器。
確定是合規(guī)的html文件后,會創(chuàng)建renderer進(jìn)程交由它渲染。為了加速,UI線程會提前找到/創(chuàng)建renderer進(jìn)程。
瀏覽器進(jìn)程將數(shù)據(jù)流交給renderer進(jìn)程,并且通過IPC提交navigation任務(wù)。一旦browser進(jìn)程收到renderer進(jìn)程的確認(rèn)后,navigation就完成了,接下來就是renderer進(jìn)程的加載階段。
到此,地址欄以及security indicator會展示網(wǎng)站信息,tab歷史會更新。

加載完成后,Renderer進(jìn)程向Browser進(jìn)程發(fā)送消息,通知完成。Browser進(jìn)程停止tab的loading spinner。

unload
當(dāng)發(fā)生導(dǎo)航時,Browser進(jìn)程創(chuàng)建新進(jìn)程處理導(dǎo)航,舊的renderer進(jìn)程處理unload回調(diào)

Service worker
Service worker 在renderer進(jìn)程中運(yùn)行。
網(wǎng)絡(luò)線程會檢查某個站點(diǎn)的service worker是否存在,如果存在UI線程找一個渲染進(jìn)程執(zhí)行代碼
渲染進(jìn)程
一個tab內(nèi)的所有事情都由renderer進(jìn)程處理。它的主要任務(wù)是將html,css,javascript轉(zhuǎn)成可交互頁面。在renderer進(jìn)程中,大部分工作是主線程完成,也存在一些worker線程,另外也有compositor和raster線程
加快渲染過程

解析
有了html,主線程一邊請求外鏈資源,一邊解析構(gòu)建dom樹。為了加速,preload scanner會并發(fā)加載資源。它查看html解析器生成的token,向brower進(jìn)程網(wǎng)絡(luò)線程發(fā)送請求

JS阻塞解析
當(dāng)html解析器發(fā)現(xiàn)script tag時,會暫停解析dom,去執(zhí)行js代碼,因?yàn)閖s可能會更改dom結(jié)構(gòu)。因此為了加速,可以使用script標(biāo)簽的async/defer標(biāo)志,告訴瀏覽器異步加載js代碼,避免阻塞主線程。
<link rel="preload">也可以告訴瀏覽器該資源是必須的,讓瀏覽器盡快加載。
詳見:https://developers.google.com/web/fundamentals/performance/resource-prioritization
樣式計算

Layout
回流(reflow)這一步是計算元素的幾何信息,包括坐標(biāo),大小。產(chǎn)物是Layout tree,類似于dom樹,多了可見性信息。當(dāng)display:none時,layout tree里時沒有該元素的。

Paint
有了layout 樹后,還需知道繪制先后次序。這一步主線程遍歷layout tree創(chuàng)建paint records

raster和compositing
有了元素幾何信息,繪制順序,將這些信息轉(zhuǎn)換成屏幕像素就是rasterizing。但是這樣子存在性能問題,現(xiàn)代瀏覽器有一種方法:compositing。將頁面分成多個layer,分別rasterizing,最后組合起來。
Non-fast scrollable region
有了compositor線程,當(dāng)發(fā)送滾動時,繪制就可以無需主線程。但是如果有事件回調(diào),compositor線程會將該區(qū)域標(biāo)識為Non-fast scrollable region(不能快速滑動區(qū))。當(dāng)該區(qū)域發(fā)送事件時,會將信息發(fā)給主線程。
通常,我們會通過document的事件委托處理事件,這樣的弊端是整個頁面都是Non-fast scrollable region,降低了性能。
getCoalescedEvents
可以將多個事件批量發(fā)送給主線程,有一定的性能提升。

參考
https://developer.chrome.com/blog/inside-browser-part4/