瀏覽器是如何調度進程和線程的?

最近正值秋招,面試了不少前端同窗,感悟頗多,後面我也會在公衆號爲你們分享下我做爲面試官的一些心得,以及對於我常常會問的一些問題的講解。前端

今天咱們來聊一下瀏覽器(以Chrome爲例)對線程和進程的調度,這個問題幾乎是我每次面試必問的。相信你們都看過不少面經會講 JavaScript 的執行機制,不少同窗熱衷於去背這些面經,以致於連 JavaScript 是單線程的都不知道,就開始回答宏任務、微任務了... 這種我真的特別無語,是真的理解仍是背出來的解題思路其實一看便知了。因此我建議你們不管是準備面試仍是平時積累知識,必定不要太浮躁,要從根本上理解這個問題,而不是去記這些解題思路。web

線程和進程

首先咱們來回顧下線程和進程的概念:面試

  • 進程: CPU 進行資源分配的基本單位
  • 線程: CPU 調度的最小單位

這是進程和線程最官方也是最多見的兩個定義,可是這兩個概念太抽象了,很難以理解。通俗一點講:進程能夠描述爲一個應用程序的執行程序,線程則是進程內部用來執行某個部分的程序。瀏覽器

下面再引用一段知乎的高贊回答,我感受很是有意思:安全

作個簡單的比喻:進程=火車,線程=車箱微信

  • 線程在進程下行進(單純的車箱沒法運行)
  • 一個進程能夠包含多個線程(一輛火車能夠有多個車箱)
  • 不一樣進程間數據很難共享(一輛火車上的乘客很難換到另一輛火車,好比站點換乘)
  • 同一進程下不一樣線程間數據很易共享(A車箱換到B車箱很容易)
  • 進程要比線程消耗更多的計算機資源(採用多列火車相比多個車箱更耗資源)
  • 進程間不會相互影響,一個線程掛掉將致使整個進程掛掉(一列火車不會影響到另一列火車,可是若是一列火車上中間的一節車箱着火了,將影響到全部車箱)
  • 進程能夠拓展到多機,進程最多適合多核(不一樣火車能夠開在多個軌道上,同一火車的車箱不能在行進的不一樣的軌道上)
  • 進程使用的內存地址能夠上鎖,即一個線程使用某些共享內存時,其餘線程必須等它結束,才能使用這一塊內存。(好比火車上的洗手間)-"互斥鎖"
  • 進程使用的內存地址能夠限定使用量(好比火車上的餐廳,最多隻容許多少人進入,若是滿了須要在門口等,等有人出來了才能進去)-「信號量」

應用程序如何調度進程和線程

當一個應用程序啓動時,一個進程就被建立了。應用程序可能會建立一些線程幫助它完成某些工做,但這不是必須的。操做系統會劃分出一部份內存給這個進程,當前應用程序的全部狀態都將保存在這個私有的內存空間中。網絡

當你關閉應用時,進程也就自動蒸發掉了,操做系統會將先前被佔用的內存空間釋放掉。多線程

一個程序並不必定只有一個進程,進程可讓操做系統再另起一個進程去處理不一樣的任務。當這種狀況發生時,新的進程又將佔據一塊內存空間。當兩個進程須要通訊時,它們進行進程間通信。架構

許多應用程序都被設計成以這種方式進行工做,因此當其中一個進程掛掉時,它能夠在其餘進程仍然運行的時候直接重啓。異步

多進程和多線程

理解了上面的內容,咱們再來從新梳理多進程和多線程的概念:

  • 多進程:多進程指的是在同一個時間裏,同一個計算機系統中若是容許兩個或兩個以上的進程處於運行狀態。多進程帶來的好處是明顯的,好比你能夠聽歌的同時,打開編輯器敲代碼,編輯器和聽歌軟件的進程之間絲絕不會相互干擾。
  • 多線程是指程序中包含多個執行流,即在一個程序中能夠同時運行多個不一樣的線程來執行不一樣的任務,也就是說容許單個程序建立多個並行執行的線程來完成各自的任務。

Chrome 的多進程架構

因爲瀏覽器自己沒有統一的規範,不一樣的瀏覽器之間的架構可能徹底不一樣,在瀏覽器剛被設計出來的時候,那時的網頁很是的簡單,每一個網頁的資源佔有率是很是低的,所以一個進程處理多個網頁時可行的。而後在今天,大量網頁變得日益複雜。把全部網頁都放進一個進程的瀏覽器面臨在健壯性,響應速度,安全性方面的挑戰,因此大部分現代瀏覽器都是多進程的。

從上面的圖咱們能夠很明顯的看出 Chrome 是一個多進程的架構,咱們打開一個瀏覽器時會啓動多個不一樣的進程協助瀏覽器將頁面爲咱們呈現出來:

  • 瀏覽器進程
  • 插件進程
  • GPU進程
  • 渲染進程

瀏覽器進程

瀏覽器最核心的進程,負責管理各個標籤頁的建立和銷燬、頁面顯示和功能(前進,後退,收藏等)、網絡資源的管理,下載等。

插件進程

負責每一個第三方插件的使用,每一個第三方插件使用時候都會建立一個對應的進程、這能夠避免第三方插件crash影響整個瀏覽器、也方便使用沙盒模型隔離插件進程,提升瀏覽器穩定性。

GPU進程

負責3D繪製和硬件加速

渲染進程

瀏覽器會爲每一個窗口分配一個渲染進程、也就是咱們常說的瀏覽器內核,這能夠避免單個 page crash 影響整個瀏覽器。

瀏覽器內核的多線程

瀏覽器內核就是瀏覽器渲染進程,從接收下載文件後再到呈現整個頁面的過程,由瀏覽器渲染進程負責。瀏覽器內核是多線程的,在內核控制下各線程相互配合以保持同步,一個瀏覽器一般由如下常駐線程組成:

  • GUI 渲染線程
  • 定時觸發器線程
  • 事件觸發線程
  • 異步 http請求線程
  • JavaScript 引擎線程

GUI渲染線程

GUI 渲染線程負責渲染瀏覽器界面 HTML 元素,當界面須要重繪(Repaint)或因爲某種操做引起迴流(reflow)時,該線程就會執行。

定時觸發器線程

瀏覽器定時計數器並非由 JavaScript 引擎計數的, 由於 JavaScript 引擎是單線程的, 若是處於阻塞線程狀態就會影響記計時的準確, 所以經過單獨線程來計時並觸發定時是更爲合理的方案。

事件觸發線程

當一個事件被觸發時該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理。這些事件能夠是當前執行的代碼塊如定時任務、也可來自瀏覽器內核的其餘線程如鼠標點擊、AJAX異步請求等,但因爲JS的單線程關係全部這些事件都得排隊等待JS引擎處理。

異步http請求線程

在XMLHttpRequest在鏈接後是經過瀏覽器新開一個線程請求, 將檢測到狀態變動時,若是設置有回調函數,異步線程就產生狀態變動事件放到 JavaScript引擎的處理隊列中等待處理。

Javascript引擎線程

Javascript 引擎,也能夠稱爲JS內核,主要負責處理 Javascript 腳本程序,例如V8引擎。Javascript 引擎線程理所固然是負責解析 Javascript 腳本,運行代碼。

因爲 JavaScript 是可操縱 DOM 的,若是在修改這些元素屬性同時渲染界面(即 JavaScript 線程和UI線程同時運行),那麼渲染線程先後得到的元素數據就可能不一致了。所以爲了防止渲染出現不可預期的結果,瀏覽器設置 GUI 渲染線程與 JavaScript 引擎爲互斥的關係,當 JavaScript 引擎執行時 GUI 線程會被掛起, GUI 更新會被保存在一個隊列中等到引擎線程空閒時當即被執行。

JavaScript 爲什麼設計成單線程

從上面咱們瞭解到 JavaScript 的執行是單線程的,也就是說,同一個時間只能作一件事。那麼,爲何 JavaScript 不設計成多個線程呢?這樣不是效率更高?

做爲瀏覽器腳本語言, JavaScript 的主要用途是與用戶互動,以及操做DOM。這決定了它只能是單線程,不然會帶來很複雜的同步問題。好比,假定 JavaScript 同時有兩個線程,一個線程在某個 DOM 節點上添加內容,另外一個線程刪除了這個節點,這時瀏覽器應該以哪一個線程爲準?

因此,爲了不復雜性,從一誕生, JavaScript 就是單線程,這已經成了這門語言的核心特徵,未來也不會改變。

WebWorker 多線程?

Web Worker爲Web內容在後臺線程中運行腳本提供了一種簡單的方法。線程能夠執行任務而不干擾用戶界面

那麼既然 JavaScript 自己被設計爲單線程,爲什麼還會有像 WebWorker 這樣的多線程 API 呢?咱們來看一下 WebWorker 的核心特色就明白了:

  • 建立 Worker 時, JS 引擎向瀏覽器申請開一個子線程(子線程是瀏覽器開的,徹底受主線程控制,並且不能操做DOM)
  • JS 引擎線程與 worker 線程間經過特定的方式通訊(postMessage API,須要經過序列化對象來與線程交互特定的數據)

因此 WebWorker 並不違背 JS引擎是單線程的 這一初衷,其主要用途是用來減輕cpu密集型計算類邏輯的負擔。

本文分享自微信公衆號 - 全棧大佬的修煉之路(gh_7795af32a259)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索