首頁  >  文章  >  微信小程式  >  深入解析小程式中的雙線程模型

深入解析小程式中的雙線程模型

青灯夜游
青灯夜游轉載
2022-01-30 09:00:313991瀏覽

本篇文章帶大家理解一下微信小程式中的雙執行緒模型,聊聊什麼是小程式的雙執行緒模型?為什麼小程式不使用瀏覽器的線程模型,而使用雙線程模型,希望對大家有幫助!

深入解析小程式中的雙線程模型

有過微信小程式開發經驗的朋友應該都知道「雙線程模型」這個概念,本文簡單梳理一下雙線程模型的一些科普知識,學識淺薄,若有錯誤歡迎指正。

我以前就職於「小程式·雲端開發」團隊,在對外的一些培訓和技術分享裡經常被人問到這樣一個問題:「微信小程式與Web 網站在技術層面的主要區別是什麼?”,在程式語言和範式上,小程式開發與Web 前端開發非常相似(例如都用JavaScript 語言、與HTML/CSS 非常相似的WXML/WXSS 等),可它卻沒有直接用原生的前端技術。 【相關學習推薦:小程式開發教學

與Web 網站相比,以微信為宿主的小程式更需要考慮安全性、效能等因素,保障小程式不會對微信App本身產生安全隱患,同時要盡量達到接近原生應用程式的效能和使用者體驗。這就是為什麼小程式不直接用瀏覽器的線程模型,非要自己弄一套雙線程模型最主要的兩個原因。

那什麼是小程式的雙執行緒模型呢?

理解一個新概念或技術的最好的方法就是給它一個參照物,所以要搞清楚小程式的執行緒模型,首先要對瀏覽器的執行緒模型有一定的了解。

瀏覽器是多進程的

可能每個前端工程師在剛入行的時候都不止一次地被面試官問到“怎麼理解前端的單線程?”,因為前端核心技能之一的JavaScript 語言是單執行緒的,充分理解並掌握JS單執行緒的運作方式對一個前端工程師來說是最基本的要求。但是很多初學者容易走入的一個迷思:錯誤地把 “JavaScript 單線程”理解為“瀏覽器單線程”。

事實上,瀏覽器內部架構很複雜,只不過在處理GUI 渲染線程和JavaScript 邏輯腳本線程上用了互斥、阻塞的管理模式,讓一些開發者產生了誤解。

以Chrome 瀏覽器為例,點擊右上角的設定按鈕然後進入「更多工具」->「任務管理員」會看到這樣的彈跳窗:

能看到Chrome 開啟了多個進程,包括瀏覽器進程、網路進程、GPU 進程等,這些都是通用的進程。 請注意,上圖裡有兩個標籤頁進程,Chrome 為每個標籤頁開啟了一個獨立的渲染進程( Renderer Process ),每個進程之間的資源( CPU、記憶體等)和行為( UI、邏輯等)互不共享,所以即便某個標籤頁崩潰了也不會影響其他標籤頁。

而在每個標籤頁進程中,瀏覽器會把不同的工作交給對應的線程,例如GUI 渲染線程負責把HTML 渲染成可視化的UI;JavaScript 引擎線程負責解析和運行JavaScript 程式碼邏輯;定時觸發器執行緒負責處理setTimeout/setInterval 定時器等。

多說一句,這裡有一個很容易搞混的地方,其實setTimeout/setInterval 並不是JavaScript 語言的一部分,而是運行時(最初是瀏覽器,後來Node.js 也支持)提供的能力。

GUI 渲染執行緒和JavaScript 引擎執行緒是互斥的,JavaScript 在執行期間會阻塞UI 的渲染,甚至如果腳本執行時間太長會因為頁面長時間無回應然後崩潰,正是GUI 渲染執行緒和JavaScript 引擎執行緒之間的這種互斥、阻塞的執行緒管理方式,讓一部分前端開發者以為瀏覽器是單執行緒的。

那為什麼 JavaScript 要設計成單執行緒的呢?

JavaScript 祖師爺只花了10 天就創造了這門語言,最初他的想法只是在瀏覽器中提供一些簡單的腳本邏輯用來處理用戶交互、DOM 操作等,所以從設計上必須遵循兩點:

  • 語法簡單;

  • 運行機制簡單。

在語法上,JavaScript 借鑒了 Java,但移除了許多複雜的設定,例如類型宣告、模組體系(後來加入)等。

在運作機制上,JavaScript 並沒有像 Java 那樣提供多執行緒能力,最主要就是為了避免多執行緒操作 DOM 造成 UI 衝突。例如存在多個執行緒同時操作同一個 DOM,瀏覽器該如何判斷最終的 UI 效果是採用哪個執行緒的結果?這是經典的執行緒安全性(也稱為執行緒同步)問題,在多執行緒程式設計領域有很多解決方案,例如加入鎖定機制,但這樣卻又帶來了更多的複雜性,與JavaScript 簡單易用的設計初衷相違背。

這同時也解釋了為什麼 GUI 渲染執行緒與 JavaScript 引擎執行緒是互斥的:JavaScript 程式碼有修改DOM 的權限。

當 JavaScript 程式碼執行時,GUI 渲染執行緒會被掛起,等待 JavaScript 引擎執行緒空閒時再執行,以免在渲染期間被 JavaScript 重複地修改 DOM 造成不必要的渲染壓力。採用互斥的模式等待 JavaScript 程式碼執行完畢後,可以確保渲染是最終的執行結果。所以瀏覽器的空閒(Idle)時長也成了衡量網站性能的重要指標之一,空閒時長多代表JavaScript 邏輯不密集以及DOM改動頻率低,這種情況下瀏覽器可以更快速順暢地響應用戶的交互行為,如下圖:

React Fiber就是利用idle時間進行分片任務處理。

後來,HTML5 引入了Web Worker,提供多執行緒執行JavaScript 程式碼的能力,但與其他程式語言不同的是,Worker 執行緒與主執行緒並不是平行的,而是一種主從( Master-Slave)多執行緒模型。

Worker 內的 JavaScript 程式碼不能操作 DOM,可以將其理解為線程安全的。 要記住這一點,這是後面講小程式雙執行緒模型一個重要的基礎。

那麼為什麼微信小程式不直接使用瀏覽器的執行緒模型呢?這需要從產品和技術兩個角度來比較小程式與 Web 網站的差異。

為什麼小程式不使用瀏覽器的執行緒模型

我剛接觸小程式開發時,經常「嫌棄」它跟Web 相比閹割弱化的能力、跟Vue 相比簡單到過分的文法等。當時,我幾乎覺得小程式就是微信仗著自己龐大的用戶量搞技術壟斷。

但是,隨著對技術和產品的不斷深入理解,我對小程式的態度也有了轉變,由「嫌棄」變成了敬佩,因為在充分理解了小程式的產品定位後,我發現雙執行緒模型是在小程式這類產品場景下的最優解。那小程式是一款什麼樣的產品呢?

小程式的宿主是微信,但是小程式版本的迭代是獨立的,升級更新不依賴宿主,這點跟 Web 網站是相同的。也就是說,小程式沿襲了 Web 的某些優勢,但它並不是 Web,目前 Web 相關的技術已經相當全面,能夠承載一些非常龐大的應用程序,例如 3D 地圖、遊戲等。

而小程式的定位是小而美、用完就走,不追求在微信中實現全部的Web 能力,所以和Web 來比能力上肯定差一些,同時具備一些微信提供的原生能力,如原生元件、系統層級和微信生態的API 等等。

另外,「小程式-微信」的關係跟「網站-瀏覽器」的關係不同,前者更接近CodePen、JSFiddler 這類線上程式設計平台(課裡簡稱平台)每個程式案例(簡稱案例)與平台的關係。

從技術的角度上,平台最核心的一個考慮點是為案例提供足夠能力的前提下,保證案例的邏輯不會危及平台的安全。想像一下,如果你能夠在 CodePen 上編寫一個程式來獲取 CodePen 的私密信息,可能第二天 CodePen 就崩潰然後炒掉所有員工。

在這樣的產品基調下進行技術選型,接下來就是架構師和程式設計師的工作了。

還是以 CodePen 為例,如果要你來設計這樣的程式設計平台,你會用什麼技術呢? 可能你第一個想到的是用 iframe,因為可以在 iframe 內使用全部 Web 能力。事實上 CodePen 確實用 iframe 來呈現程式的效果,但是並不會把輸入的 JavaScript 程式碼完全拷貝到 iframe 內運行,而是程式碼會經過一次編譯流程之後才會被注入 iframe 內。這樣做的出發點主要是基於安全的考慮,在編譯過程中將一些危險的程式碼剔除;其次這樣做還能在平台中支援更多語言,例如typescript。當然,還有性能,性能問題是 iframe 老生常談的問題了,我就不多說了。

所以,不僅要使用 iframe,還需要引入額外的 JavaScript 編譯器。 CodePen 一定要確保每個案例的JavaScript 程式碼是線程安全的,最基本的就是要禁止程式操作CodePen 網站的DOM ,實現這一點有兩個方法:

  • 一個是Web Worker;

  • 另一個是使用Shadow DOM。

Web Worker 是執行緒安全的,Worker 內的 JavaScript 程式碼無法取得 Window 和 Document 對象,也就無法操作 DOM。除此之外,由於 Worker 的線程安全特性,Worker 內的程式碼運行過程中不會阻塞外層的 GUI 渲染線程,兩者可以並行。

Shadow DOM 是 Web Components 規範的一部分,將 ShadowRoot 的模式設為 closed 就可以禁止取得到 ShadowRoot 節點,因此無法操作其內部的 DOM。

兩者相比,Shadow DOM 的相容性比 Web Worker 更差,距離大規模使用的日期還很遙遠,所以 Web Worker 的方案更現實一點。

這樣就形成了一個簡易的雙線程模型:Worker 線程負責計算,將結果透過 postMessage 傳遞給主線程,主線程負責渲染。

但這個模型有比較嚴重的效能問題,Web Worker 非常耗費資源,除去運算消耗以外,與主執行緒的通訊過程對效能的損耗也非常嚴重。

那有沒有辦法實現跟 Web Worker 一樣的執行緒安全,同時又兼顧效能保證良好的使用者體驗呢?這便是微信小程式採用雙線程模型的主要目的。

安全高效的雙執行緒模型

雖然前面用了CodePen 這類程式設計平台做類比,但小程式與CodePen 的技術需求並不完全相同,主要區別在於小程式並不需要支援所有的HTML 標籤,只提供有限的幾類UI 元件,根據小程式產品定位,我們可以歸納出小程式的主要技術需求可以歸納為下面這樣幾點。 (任何新技術或架構都是為了解決特定的問題,所以有必要了解小程式的主要技術需求。)

  • 限制UI 元件類型,只允許聲明指定的幾個元件

    小程式在宣告元件時並不是使用原生的HTML 標籤,而是只能夠透過微信提供的幾種內建基礎元件,當然你也可以自訂元件,但也是透過內建基礎組件的組合來實現。

  • 保證邏輯執行緒安全,不允許直接操作UI 元件

    小程式更新UI 的方式與Vue/React 等MVVM 框架類似, JavaScript 程式碼不能直接操作DOM(只做類比,事實上小程式中沒有DOM的概念),而是透過更新狀態( setState )的方式非同步更新UI ,這個過程中會用到VDOM 和高效的diff 演算法(這兩點並不是我們要討論的內容,你課程下可以自己搜尋相關資料)。

  • 能夠在線上更新,不依賴微信

    小程式的宿主是微信,如果使用純Native 實現,那麼小程式的版本更新必須依賴微信,跟微信的程式碼一起發版,這樣一定是不行的。如果是純 Web 實現,安全性和效能就很難得到保障。

    小程式需要既能夠像 Web 一樣將資源託管在雲端,更新獨立;同時又能夠保證足夠好的安全性和效能。所以最終小程式採用了一種混合的架構模式:使用 Webview 渲染 UI、使用類似Web Worker 的獨立執行緒運行邏輯,這就是雙執行緒模型

  • 效能需盡量提升,保證使用者經驗

    #前面提到的基於Web Worker 的簡易雙執行緒模型效能是很大的問題,小程式的雙線程模型並不是使用Web Worker 子線程,而是一個獨立的“主線程”,這樣能夠保證相對較好的效能。

渲染線程和邏輯線程

小程式的雙線程指的就是渲染線程和邏輯線程,這兩個線程分別承擔UI的渲染和執行JavaScript 程式碼的工作。如下圖所示:

渲染執行緒使用 Webview 進行 UI 的渲染呈現。 Webview 是一個完整的類別瀏覽器運行環境,本身俱備運行JavaScript 的能力,但是小程式並不是將邏輯腳本放到Webview 中運行,而是將邏輯層獨立為一個與Webview 平行的線程,使用客戶端提供的JavaScript 引擎運行程式碼,iOS 的JavaScriptCore、安卓是騰訊X5 核心提供的JsCore 環境以及IDE 工具的nwjs 。

邏輯執行緒是一個只能夠運行 JavaScript 的沙箱環境,不提供 DOM 操作相關的 API,所以不能直接操作 UI,只能夠透過 setData 更新資料的方式非同步更新 UI。

事件驅動的通訊方式

注意上圖渲染執行緒和邏輯執行緒之間的通訊方式,與Vue/React 不同的是,小程序的渲染層與邏輯層之間的通訊並不是在兩者之間直接傳遞資料或事件,而是由Native 作為中間媒介進行轉發

整個過程是典型的事件驅動模式:

  • #渲染層(也可以稱為視圖層)透過與使用者的互動觸發特定的事件event;

  • 然後event 被傳遞給邏輯層;

  • 邏輯層繼而透過一系列的邏輯處理、資料請求、接口呼叫等行為將加工好的資料data 傳遞給渲染層;

  • 最後渲染層將data 渲染為可視化的UI。

這種資料驅動UI 的模式是現在前端程式設計領域較為推崇的程式設計範式,如果你是一個超過5 年開發經驗的前端開發者的話,那麼我相信在最初接觸到這種模式的時候肯定有一些不適應,因為在此之前JavaScript 操作DOM 幾乎是一種“業內規則”,甚至有不少針對前端入門的圖書、博客和教材都是先從DOM 操作講起,現在看來這些確實有些不合時宜了。

而這樣邏輯與渲染分離的線程分工模式一方面能夠保證運行在邏輯線程沙箱內的JavaScript 程式碼是線程安全的,另一方面由於渲染線程的計算量非常小從而保證了對使用者互動行為的快速回應,提高了使用者體驗。

總的來說,跟瀏覽器的線程模型相比,小程式的雙線程模型解決了或者說規避了Web Worker 堪憂的性能同時又實現了與Web Worker 相同的線程安全,從性能和安全兩個角度實現了提升。可以概括地說,雙執行緒模式是受限於瀏覽器現有的進程和執行緒管理模式之下,在小程式這一具體場景之內的一種改進的架構方案

總結

在我看來,程式設計師的核心能力和競爭力並不是充分了解某種語言或框架的 API ,而是這些語言和框架底層的原理知識。對一個小程式的開發者來說,在工作中遇到技術難題時的解決方案往往是基於底層原理的(甚至更直白一點,當你找工作面試時,沒人會問你小程式的語法)。

透過了解小程式雙執行緒模型的背景、設計、通信,希望能夠讓大家更深入地理解小程式的底層架構,如果在後續工作中有類似場景的需求也可以作為借鑒。當然,了解小程式的雙執行緒模型並不是唯一的目標,這些知識在某種程度上能對日常開發工作產生一些啟示,主要是效能方面:

  • 在保證功能的前提下盡量使用結構簡單的UI;

  • 盡量降低JavaScript 邏輯的複雜度;

  • 盡量減少setData 的呼叫頻次和攜帶的數據體積。

更多程式相關知識,請造訪:程式設計影片! !

以上是深入解析小程式中的雙線程模型的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:cnblogs.com。如有侵權,請聯絡admin@php.cn刪除