瀏覽器多執行緒和js單執行緒

小山芋發表於2019-04-03

一、什麼是程式和執行緒

在涉及瀏覽器多執行緒和js單執行緒之前,我們先鋪墊一下前置概念:

1、程式(process)

程式和執行緒都是作業系統的概念。

程式是應用程式的執行例項,每個程式是由私有的虛擬地址空間、程式碼、資料和其它各種系統資源組成,即程式是作業系統進行資源分配和獨立執行的最小單元。

當我們啟動一個應用,計算機會至少建立一個程式,cpu會為程式分配一部分記憶體,應用的所有狀態都會儲存在這塊記憶體中,應用也許還會建立多個執行緒來輔助工作,這些執行緒可以共享這部分記憶體中的資料。如果應用關閉,程式會被終結,作業系統會釋放相關記憶體。

mac電腦可以在活動監視器中檢視啟動的程式數:

瀏覽器多執行緒和js單執行緒
活動監視器

2、執行緒(thread)

  • 程式內部的一個執行單元,是被系統獨立排程和分派的基本單位。系統建立好程式後,實際上就啟動執行了該程式的主執行執行緒

  • 程式就像是一個有邊界的生產廠間,而執行緒就像是廠間內的一個個員工,可以自己做自己的事情,也可以相互配合做同一件事情,所以一個程式可以建立多個執行緒。

  • 執行緒自己不需要系統重新分配資源,它與同屬一個程式的其它執行緒共享當前程式所擁有的全部資源。 PS: 程式之間是不共享資源和地址空間的,所以不會存在太多的安全問題,而由於多個執行緒共享著相同的地址空間和資源,所以會存線上程之間有可能會惡意修改或者獲取非授權資料等複雜的安全問題。

而現在通用叫法單執行緒與多執行緒,都是指在一個程式內的單和多。

如果對程式及執行緒的理解還存在疑惑,可以參考下述文章? www.ruanyifeng.com/blog/2013/0…

關於單核處理器、多核處理器、多處理器是怎麼處理程式和執行緒的,可以參考下述文章? blog.csdn.net/alinshen/ar… jsonliangyoujun.iteye.com/blog/235827…

二、瀏覽器的多程式

其實如果要開發一個瀏覽器,它可以是單程式多執行緒的應用,也可以是使用 IPC 通訊的多程式應用。

瀏覽器多執行緒和js單執行緒

不同瀏覽器採用了不同的架構模式,這裡我們們只研究以Chrome為代表的瀏覽器:

Chrome 採用多程式架構

瀏覽器多執行緒和js單執行緒
Chrome 的不同程式

每開啟一個tab頁,就相當於於建立了一個獨立的瀏覽器程式,這一點從上面的圖中可以看出,但是也不是絕對的,它也有自己的優化機制,有的程式可能會被合併。

1、Chrome 的主要程式及其職責

  • Browser Process 瀏覽器的主程式(負責協調、主控) (1)負責包括位址列,書籤欄,前進後退按鈕等部分的工作 (2)負責處理瀏覽器的一些不可見的底層操作,比如網路請求和檔案訪問 (3)負責各個頁面的管理,建立和銷燬其他程式
  • Renderer Process 負責一個 tab 內關於網頁呈現的所有事情,頁面渲染,指令碼執行,事件處理等
  • Plugin Process 負責控制一個網頁用到的所有外掛,如 flash 每種型別的外掛對應一個程式,僅當使用該外掛時才建立
  • GPU Process 負責處理 GPU 相關的任務

瀏覽器多執行緒和js單執行緒
不同程式負責的瀏覽器區域示意圖

Chrome 還為我們提供了「工作管理員」,供我們方便的檢視當前瀏覽器中執行的所有程式及每個程式佔用的系統資源,雙擊還可以檢視更多類別資訊。

通過「頁面右上角的三個點點點 — 更多工具 — 工作管理員」即可開啟相關皮膚。

瀏覽器多執行緒和js單執行緒
工作管理員

2、Chrome 多程式架構的優缺點

優點: (1)某一渲染程式出問題不會影響其他程式 (2)更為安全,在系統層面上限定了不同程式的許可權

缺點: (1)由於不同程式間的記憶體不共享,不同程式的記憶體常常需要包含相同的內容。為了節省記憶體,Chrome 限制了最多的程式數,最大程式數量由裝置的記憶體和 CPU 能力決定,當達到這一限制時,新開啟的 Tab 會共用之前同一個站點的渲染程式。

Chrome 把瀏覽器不同程式的功能看做服務,這些服務可以方便的分割為不同的程式或者合併為一個程式。

以 Broswer Process 為例,如果 Chrome 執行在強大的硬體上,它會分割不同的服務到不同的程式,這樣 Chrome 整體的執行會更加穩定,但是如果 Chrome 執行在資源貧瘠的裝置上,這些服務又會合併到同一個程式中執行,這樣可以節省記憶體。

三、瀏覽器的多執行緒

對我們fe來講,最重要的是Renderer Process下的多執行緒,就是我們常說的瀏覽器核心。

Chrome瀏覽器為每個tab頁面單獨啟用程式,因此每個tab網頁都有由其獨立的渲染引擎例項

瀏覽器核心是多執行緒,在核心控制下各執行緒相互配合以保持同步,一個瀏覽器通常由以下常駐執行緒組成:

1、GUI 渲染執行緒

負責渲染瀏覽器介面HTML元素,當介面需要重繪(Repaint)或由於某種操作引發迴流(reflow)時,該執行緒就會執行。在Javascript引擎執行指令碼期間,GUI渲染執行緒都是處於掛起狀態的,也就是說被”凍結”了.

ps:重排和重繪的區別,有興趣的話,請看?的連結 juejin.im/entry/582f1…

2、JavaScript引擎執行緒

JS核心,負責處理Javascript指令碼程式。 一直等待著任務佇列中任務的到來,然後解析Javascript指令碼,執行程式碼。一個Tab頁(renderer程式)中無論什麼時候都只有一個JS執行緒在執行JS程式。

ps: GUI渲染執行緒與JS引擎執行緒是互斥的,所以如果JS執行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染載入阻塞。

3、定時觸發器執行緒

  • 定時器setInterval與setTimeout所線上程
  • 瀏覽器定時計數器並不是由JavaScript引擎計數的 因為JavaScript引擎是單執行緒的, 如果處於阻塞執行緒狀態就會影響記計時的準確, 因此通過單獨執行緒來計時並觸發定時是更為合理的方案。

4、事件觸發執行緒

  • 用來控制事件輪詢,JS引擎自己忙不過來,需要瀏覽器另開執行緒協助
  • 當JS引擎執行程式碼塊如滑鼠點選、AJAX非同步請求等,會將對應任務新增到事件觸發執行緒中
  • 當對應的事件符合觸發條件被觸發時,該執行緒會把事件新增到待處理任務佇列的隊尾,等待JS引擎的處理
  • 由於JS的單執行緒關係,所以這些待處理佇列中的事件都得排隊等待JS引擎處理(當JS引擎空閒時才會去執行)

5、非同步http請求執行緒

在XMLHttpRequest在連線後是通過瀏覽器新開一個執行緒請求, 將檢測到狀態變更時,如果設定有回撥函式,非同步執行緒就產生狀態變更事件放到 JavaScript引擎的處理佇列中等待處理。

let xhr = new XMLHttpRequest();   // 不相容ie6及更低,建立ajax例項
  xhr.open('get',`json/banner.json?_${Math.random()}`); //開啟請求:傳送請求前的一些配置項,get有快取,所以要加隨機數,post不用
  xhr.onreadystatechange = ()=>{
     // 事件監聽,ajax狀態改變事件,基於這個事件可以獲取伺服器返回的響應頭主體內容(響應頭先回來)
     // 從這步開始,當前ajax任務開始,如果是同步的,後續程式碼不執行,要等到ajax狀態成功後再執行,如果是非同步的,不會
      if(xhr.readyState === 4 && /^(2|3)\d{2}$/.test(xhr.status)){
          //readyState 請求狀態   // status 返回狀態
          let data = JSON.parse(xhr.responseText);  // 獲取響應主體的內容
      }
  };
  xhr.send(null); // 傳送 ajax (括號中傳遞的內容就是請求主體的內容)
複製程式碼

這是一個簡單的XMLHttpRequest請求的四個步驟,基於XMLHttpRequest的代表產品$.ajax,axios等

三、Js為什麼要是單執行緒?

js的單執行緒和它的用途有關,作為瀏覽器指令碼語言,它主要是用來處理頁面中使用者的互動,以及操作DOM樹、CSS樣式樹來給使用者呈現一份動態而豐富的互動體驗和伺服器邏輯的互動處理。

如果JavaScript是多執行緒的方式來操作這些UI DOM,則可能出現UI操作的衝突;

如果Javascript是多執行緒的話,在多執行緒的互動下,處於UI中的DOM節點就可能成為一個臨界資源,假設存在兩個執行緒同時操作一個DOM,一個負責修改一個負責刪除,那麼這個時候就需要瀏覽器來裁決如何生效哪個執行緒的執行結果。

當然我們可以通過鎖來解決上面的問題。但為了避免因為引入了鎖而帶來更大的複雜性,Javascript在最初就選擇了單執行緒執行

這也解釋了為什麼GUI執行緒和JS引擎是互斥的。

當JS引擎執行時GUI執行緒會被掛起,GUI更新則會被儲存在一個佇列中等到JS引擎執行緒空閒時立即被執行。

為了多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript指令碼建立多個執行緒,但是子執行緒完全受主執行緒控制,且不得操作DOM。所以,這個新標準並沒有改變JavaScript單執行緒的本質。

四、Js的單執行緒會帶來什麼問題?怎麼處理的?

當呼叫棧中的函式呼叫需要花費我們非常多的時間,會發生什麼?

比如正在執行一個複雜的影象轉換的演算法

當呼叫棧有函式在執行,瀏覽器就不能做任何事了 —— 它被阻塞了。這意味著瀏覽器不能渲染頁面,不能執行任何其它的程式碼,它就這樣被卡住了。那麼問題來了 —— 你的應用不再高效和令人滿意了。

一旦你的瀏覽器開始在呼叫棧執行很多很多的任務,它就很有可能會長時間得不到響應。在這一點上,大多數的瀏覽器會採取丟擲錯誤的解決方案,詢問你是否要終止這個頁面:

瀏覽器多執行緒和js單執行緒
但這會毀了你的使用者體驗

js開發的時候有一個很重要概念:

之後 的程式碼並不一定會在 現在 的程式碼執行之後執行。換句話說,在定義中不能 現在 立刻完成的任務將會非同步執行,這意味著可能不會像你認為的那樣發生上面所說的阻塞問題。

誰會告訴 JS 引擎去執行你的程式?事實上,JS 引擎不是單獨執行的 —— 它執行在一個宿主環境中,對於大多數開發者來說就是典型的瀏覽器和 Node.js。實際上,如今,JavaScript 被應用到了從機器人到燈泡的各種裝置上。每個裝置都代表了一種不同型別的 JS 引擎的宿主環境。

所有的環境都有一個共同點,就是都擁有一個事件迴圈內建機制,它隨著時間的推移每次都去呼叫 JS 引擎去處理程式中多個塊的執行。

這意味著 JS 引擎只是任意的 JS 程式碼按需執行的環境。是它周圍的環境來排程這些事件(JS 程式碼執行)。,其他周圍環境就是指的在同一程式下的不同的執行緒,比如事件觸發執行緒、定時器執行緒等

所以,比如當你的 JavaScript 程式發出了一個 Ajax 請求去伺服器獲取資料,你在一個函式(回撥)中寫了 “response” 程式碼,然後 JS 引擎就會告訴宿主環境: “嘿,我現在要暫停執行了,但是當你完成了這個網路請求,並且獲取到資料的時候,請回來呼叫這個函式。”

然後瀏覽器設定對網路響應的監聽,當它有東西返回給你的時候,它將會把回撥函式插入到事件迴圈佇列裡然後執行。

瀏覽器多執行緒和js單執行緒
瀏覽器種的EventLoop

如果你想詳細的瞭解這方面的內容,?的連結: web.jobbole.com/95613/

相關文章