從程式和執行緒瞭解瀏覽器的工作原理

weixin_33807284發表於2018-06-22

程式和執行緒

程式(process)和執行緒(thread)是作業系統的基本概念。

現代作業系統都是可以同時執行多個任務的,比如:用瀏覽器上網的同時還可以聽音樂。對於作業系統來說,一個任務就是一個程式,比如開啟一個瀏覽器就是啟動了一個瀏覽器程式,開啟一個 Word 就啟動了一個 Word 程式。

有些程式同時不止做一件事,比如 Word,它同時可以進行打字、拼寫檢查、列印等事情。在一個程式內部,要同時做多件事,就需要同時執行多個“子任務”,我們把程式內的這些“子任務”稱為執行緒。

由於每個程式至少要做一件事,所以一個程式至少有一個執行緒。

系統會給每個程式分配獨立的記憶體,因此程式有它獨立的資源。同一程式內的各個執行緒之間共享該程式的記憶體空間(包括程式碼段,資料集,堆等)。

如果電腦是 windows 系統,開啟工作管理員,可以看到有一個後臺程式列表,在這裡我們可以看到每個程式的記憶體資源資訊以及 CPU 佔有率。

4359173-cf50a4808d884514.jpeg

我們再用官方的術語描述一下:

程式是 CPU 資源分配的最小單位(是能擁有資源和獨立執行的最小單位)。

執行緒是 CPU 排程的最小單位(是建立在程式基礎上的一次程式執行單位)。

瀏覽器是多程式的

理解了程式和執行緒之後,接下來我們對瀏覽器進行一定程度上的認識。

瀏覽器是多程式的,每開啟一個 tab 頁,就相當於建立了一個獨立的瀏覽器程式。

4359173-41a0a2e1c14e9499.png

圖中開啟了 Chrome 瀏覽器的多個 tab 頁,在 Chrome 工作管理員中可以看到有多個程式,每一個 tab 頁有一個獨立的程式。

注意:瀏覽器應該也有自己的優化機制,有時候開啟多個 tab 頁,在 Chrome 工作管理員中會看到有些程式被合併了,所以每個 tab 頁對應一個程式並不一定是絕對的。

瀏覽器包含哪些程式?

為了簡化理解,這裡僅列舉主要程式。

  • Browser 程式:瀏覽器的主程式,只有一個。

    • 負責瀏覽器介面的顯示與互動;

    • 各個頁面的管理,建立和銷燬其他程式;

    • 網路的資源管理、下載等。

  • Renderer 程式:也稱為瀏覽器渲染程式瀏覽器核心,內部是多執行緒的。主要負責頁面渲染,指令碼執行,事件處理等。

  • 第三方外掛程式:每種型別的外掛對應一個程式,僅當使用該外掛時才建立。

  • GPU 程式:最多一個,用於 3D 繪製等。

瀏覽器多程式的優勢

  • 由於預設 新開 一個 tab 頁面 新建 一個程式,所以單個 tab 頁面崩潰不會影響到整個瀏覽器;

  • 同樣,第三方外掛崩潰也不會影響到整個瀏覽器;

  • 多程式可以充分利用現代 CPU 多核的優勢;

  • 方便使用沙盒模型隔離外掛等程式,提高瀏覽器的穩定性。

系統為瀏覽器新開的程式分配記憶體、CPU 等資源,所以記憶體和 CPU 的資源消耗也會更大。

瀏覽器核心(渲染程式)

前面說了這麼多的程式,對普通前端操作來說,最重要的還是渲染程式。

瀏覽器的渲染程式是多執行緒的,頁面的渲染,JS的執行,事件的迴圈等,都在這個程式內執行。

渲染程式通常由以下常駐執行緒組成:

1. GUI 渲染執行緒

負責渲染瀏覽器介面,解析 HTML、CSS,構建 DOM tree和 render tree,佈局和繪製等。當介面需要重繪(repaint)或由於某種操作引發迴流(reflow)時,該執行緒就會執行。

2. JS 引擎執行緒

也稱為 JS 核心,負責解析 JavaScript 指令碼,執行程式碼。

  • JavaScript 是單執行緒的

    JavaScript 為什麼是單執行緒的?這與它的用途有關。JavaScript 作為瀏覽器指令碼語言,主要用途是與使用者互動以及操作 DOM。這也決定了它只能是單執行緒的,否則會帶來很複雜的同步問題。想想一下,如果 JavaScript 同時有連個執行緒,一個執行緒在某個 DOM 節點上新增內容,另一個執行緒刪除了這個 DOM 節點,這時瀏覽器應該以哪個執行緒為準呢?所以,為了避免複雜性,JavaScript 從一開始就是單執行緒。

  • GUI 渲染執行緒 與 JS 引擎執行緒是互斥的

    由於 JavaScript 可以操作 DOM,如果在修改元素屬性的同時渲染介面(即 JavaScript 引擎執行緒和 GUI 渲染執行緒同時執行),那麼渲染執行緒前後獲得的元素資料就可能會不一致。因此,為了防止渲染出現不可預期的結果,瀏覽器設定 GUI 渲染執行緒與 JS 引擎為互斥的關係。當 JS 引擎執行時,GUI 執行緒被掛起,GUI 更新被儲存在一個佇列中,等到 JS 引擎執行緒空閒時立即被執行。

  • JS 阻塞頁面載入

    由於 GUI 渲染執行緒與 JS 引擎執行緒是互斥的,當瀏覽器在執行 JavaScript 的時候,GUI 渲染執行緒會被儲存在一個佇列中,直到 JS 程式執行完成,才會接著執行。因此如果 JS 執行時間過長,就會造成頁面的渲染不連貫,導致頁面渲染載入阻塞。

3. 事件觸發執行緒

當一個事件被觸發時,該執行緒會把事件新增到待處理佇列的隊尾,等待 JS 引擎處理。這些事件可以是當前執行的程式碼塊,如定時任務;也可以是來自瀏覽器核心的其他執行緒,如:滑鼠點選、Ajax非同步請求等。但由於 JS 是單執行緒的,這些事件都需要排隊等待 JS 引擎處理。

4. 定時觸發器執行緒

setTimeoutsetInterval 所在的執行緒。瀏覽器定時計數器並不是由 JS 引擎計數的,因為 JS 是單執行緒的,如果處於阻塞執行緒狀態就會影響計時的準確,所以通過單獨的執行緒來計時並觸發定時更為合理。

5. 非同步 http 請求執行緒

XMLHttpRequest 在建立連線後,通過瀏覽器新開一個執行緒請求,一旦檢測到狀態變更並且設定有回撥函式,非同步執行緒就產生狀態變更事件,將這個回撥再放入事件佇列中,等待 JS 引擎空閒時處理。

Browser 程式和 Renderer 程式的通訊過程

開啟瀏覽器的一個 tab 頁時,我們看下其中的大致過程:

  • Browser 程式收到使用者請求,通過網路下載獲取頁面內容,然後將該任務通過RendererHost介面傳遞給 Renderer 程式;

  • Renderer 程式的 Renderer 介面收到訊息,簡單解釋後,交給 GUI 渲染執行緒開始渲染;

    • GUI 渲染執行緒接收請求,載入網頁並渲染網頁,這個過程中可能需要 Browser 程式獲取資源和 GPU 程式來幫助渲染,也可能會有 JS 引擎執行緒操作 DOM(可能造成迴流並重繪);

    • 最後 Renderer 程式將結果傳遞給 Browser 程式;

  • Browser 程式接收到結果,並將結果繪製出來。

到這裡應該對瀏覽器的運作有一定理解了,我們再來看下瀏覽器是怎麼渲染頁面的。

瀏覽器的渲染流程

瀏覽器核心拿到頁面內容後,渲染過程大概分為以下幾個部分:

4359173-07ecdf1c840111b3.png
  1. 解析 HTML 檔案,生成 DOM tree;同時解析 CSS 檔案以及樣式元素中的樣式資料,生成 CSS Rules。
  2. 構建 render tree:根據 DOM tree 和 CSS Rules 來構建 render tree,它可以讓瀏覽器按照正確的順序繪製內容。
  3. 佈局(layout / reflow):計算各元素尺寸、位置。
  4. 繪製(paint):繪製頁面畫素資訊。
  5. 瀏覽器將各層資訊傳送給 GPU,GPU 將各層資訊合成(composite),顯示在螢幕上。

補充:
Webkit 將 render tree 中的元素稱為 render object (或 renderer),每一個 render object 都代表一個的矩形區域,通常對應於相關節點的 CSS 框,這些矩形的排列順序就是它們在螢幕上顯示的順序。

Render object 和 DOM 節點是相對應的,但並非一一對應。非視覺化的 DOM 元素不會插入 render tree 中,例如“head”元素 和 一些 display: none 的節點就沒必要放在 render tree 中了。

這裡只是大致的過程,詳細步驟可以看參考資料中的第一篇。
渲染完成後,接下來就是 JavaScript 邏輯處理了。

參考資料

相關文章