採用非主執行緒架構可大幅提升應用程式的穩定性和使用者體驗。
過去 20 年來,網路從只有幾種樣式和圖片的靜態文件,大幅演進為複雜的動態應用程式。不過,有一件事大致上沒有改變:每個瀏覽器分頁只有一個執行緒 (少數例外),負責算繪網站和執行 JavaScript。
因此主執行緒的工作量大幅增加。隨著網頁應用程式日益複雜,主要執行緒會成為效能的重大瓶頸。更糟的是,在特定使用者的主執行緒上執行程式碼所需的時間幾乎完全無法預測,因為裝置功能對效能有巨大影響。隨著使用者透過越來越多種裝置存取網路,從極度受限的功能型手機到高功率、高更新率的旗艦機,這種不可預測性只會越來越高。
如果我們希望複雜的網頁應用程式能可靠地符合效能指南 (例如網站使用體驗核心指標,這是根據人類感知和心理學的實證資料而定),就必須設法在主執行緒 (OMT) 以外執行程式碼。
為什麼要使用 Web Worker?
根據預設,JavaScript 是單一執行緒語言,會在主執行緒上執行工作。不過,網頁工作站可讓開發人員建立獨立執行緒來處理主執行緒以外的工作,藉此提供主執行緒的某種逃生出口。雖然網頁背景工作執行緒的範圍有限,無法直接存取 DOM,但如果需要完成大量工作,否則會造成主執行緒負擔過重,網頁背景工作執行緒就非常實用。
就網站體驗核心指標而言,在主執行緒外執行作業可能很有幫助。具體來說,將工作從主執行緒卸載至網頁工作人員,可以減少主執行緒的爭用,進而改善網頁的「與下一個繪製內容互動」 (INP) 回應性指標。主執行緒要處理的工作越少,就能越快回應使用者互動。
減少主執行緒工作 (尤其是在啟動期間),也有助於減少長時間工作,進而提升最大內容繪製 (LCP)。算繪 LCP 元素需要主執行緒時間 (無論是算繪文字或圖片,這都是常見的 LCP 元素),因此減少主執行緒的整體工作量,可確保網頁的 LCP 元素較不容易遭到耗用大量資源的工作封鎖,而這類工作可改由 Web Worker 處理。
使用 Web Worker 執行緒
其他平台通常支援平行作業,可讓您為執行緒提供函式,與程式的其餘部分平行執行。您可以在這兩個執行緒中存取相同的變數,並使用互斥鎖和信號燈同步處理這些共用資源的存取權,防止發生競爭狀況。
在 JavaScript 中,我們可以從網頁工作人員取得大致類似的功能,這項功能自 2007 年推出以來,已在 2012 年開始支援所有主要瀏覽器。Web Worker 會與主執行緒平行執行,但與 OS 執行緒不同,無法共用變數。
如要建立 Web Worker,請將檔案傳遞至 Worker 建構函式,這會開始在個別執行緒中執行該檔案:
const worker = new Worker("./worker.js");
使用 postMessage API 傳送訊息,與 Web Worker 通訊。在 postMessage 呼叫中將訊息值做為參數傳遞,然後在背景工作人員中新增訊息事件監聽器:
main.js
const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);
worker.js
addEventListener('message', event => {
const [a, b] = event.data;
// Do stuff with the message
// ...
});
如要將訊息傳回主執行緒,請在網頁工作人員中使用相同的 postMessage API,並在主執行緒上設定事件監聽器:
main.js
const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);
worker.addEventListener('message', event => {
console.log(event.data);
});
worker.js
addEventListener('message', event => {
const [a, b] = event.data;
// Do stuff with the message
postMessage(a + b);
});
誠然,這種做法有些限制。以往,網頁工作站主要用於將單一繁重工作移出主執行緒。嘗試使用單一網頁工作人員處理多項作業很快就會變得難以管理:您不僅必須在訊息中編碼參數,還必須編碼作業,而且必須進行記帳,才能將回應與要求相符。這種複雜性可能就是網頁工作站未獲得更廣泛採用的原因。
但如果能降低主執行緒和網頁背景工作之間的通訊難度,這個模型就很適合許多用途。幸運的是,我們有這樣的程式庫!
Comlink:讓網頁工作站更輕鬆
Comlink 程式庫的目標是讓您使用 Web Worker,不必考慮 postMessage 的詳細資料。Comlink 可讓您在網頁工作人員和主執行緒之間共用變數,幾乎就像其他支援執行緒的程式設計語言一樣。
如要設定 Comlink,請在網頁背景工作人員中匯入 Comlink,並定義一組要向主執行緒公開的函式。接著,您可以在主執行緒上匯入 Comlink、包裝工作人員,並存取公開函式:
worker.js
import {expose} from 'comlink';
const api = {
someMethod() {
// ...
}
}
expose(api);
main.js
import {wrap} from 'comlink';
const worker = new Worker('./worker.js');
const api = wrap(worker);
主執行緒上的 api 變數行為與網頁工作人員中的變數相同,但每個函式都會傳回值的 Promise,而非值本身。
您應該將哪些程式碼移至網頁工作人員?
Web Worker 無法存取 DOM 和許多 API,例如 WebUSB、WebRTC 或 Web Audio,因此您無法將依賴這類存取權的應用程式片段放在 Worker 中。不過,每段移至 Worker 的程式碼,都能為主要執行緒爭取更多空間,處理「必須」執行的工作,例如更新使用者介面。
對網頁開發人員來說,大部分網頁應用程式都依賴 Vue 或 React 等 UI 架構來協調應用程式中的所有項目,因此所有項目都是架構的元件,本質上與 DOM 息息相關。這似乎會導致難以遷移至 OMT 架構。
不過,如果我們改用將 UI 問題與其他問題 (例如狀態管理) 分開的模型,即使是採用架構的應用程式,Web Worker 也能發揮相當大的作用。PROXX 就是採用這種做法。
PROXX:OMT 案例研究
Google Chrome 團隊開發了 PROXX,這款遊戲是滿足漸進式網頁應用程式需求的踩地雷複製版,包括離線運作和提供引人入勝的使用者體驗。可惜的是,遊戲的早期版本在功能手機等資源受限的裝置上效能不佳,因此團隊發現主執行緒是瓶頸。
該團隊決定使用 Web Worker,將遊戲的視覺狀態與邏輯分開:
- 主執行緒會處理動畫和轉場效果的算繪作業。
- 網頁工作站會處理遊戲邏輯,這純粹是運算作業。
OMT 對 PROXX 的功能型手機效能有有趣的影響。在非 OMT 版本中,使用者與 UI 互動後,UI 會凍結六秒。沒有任何意見回饋,使用者必須等待整整六秒才能執行其他操作。
但在 OMT 版本中,遊戲需要 12 秒才能完成 UI 更新。雖然這看似會導致效能降低,但實際上會增加提供給使用者的意見回饋。應用程式傳送的影格數量比非 OMT 版本多,而後者完全不會傳送任何影格,因此才會發生速度變慢的情況。因此,使用者會知道發生了某些事,並可在 UI 更新時繼續玩遊戲,大幅提升遊戲體驗。
這是經過審慎考量的取捨:我們為資源受限的裝置提供感覺更好的體驗,同時不會懲罰高階裝置的使用者。
OMT 架構的影響
如 PROXX 範例所示,OMT 可讓應用程式在更多裝置上穩定執行,但不會加快應用程式速度:
- 您只是將工作移出主執行緒,並未減少工作量。
- 網頁工作人員和主執行緒之間額外的通訊負擔,有時可能會導致速度稍微變慢。
利弊評估
由於 JavaScript 執行時,主執行緒可自由處理捲動等使用者互動,因此即使總等待時間可能略長,捨棄的影格也會減少。與捨棄影格相比,讓使用者稍等一下是較好的做法,因為捨棄影格的誤差範圍較小:捨棄影格發生在毫秒內,而使用者要過數百毫秒才會感受到等待時間。
由於不同裝置的效能難以預測,OMT 架構的目標實際上是降低風險,讓應用程式在執行階段條件高度變動的情況下更加穩健,而不是為了平行處理的效能優勢。彈性提升和使用者體驗改善的價值,遠遠超過速度方面的小幅取捨。
工具注意事項
Web Worker 尚未成為主流,因此大多數模組工具 (例如 webpack 和 Rollup) 都不支援這項功能。(但Parcel可以!)幸好,有外掛程式可讓網頁工作站與 webpack 和 Rollup「合作」運作:
- 適用於 webpack 的 worker-plugin
- 適用於 Rollup 的 rollup-plugin-off-main-thread
總結
為確保應用程式盡可能可靠且容易存取 (尤其是在日益全球化的市場中),我們需要支援資源受限的裝置,因為全球大多數使用者都是透過這類裝置存取網路。OMT 是一種很有前景的技術,可提升這類裝置的效能,同時不會對高階裝置使用者造成負面影響。
此外,OMT 還具備下列次要優點:
- 將 JavaScript 執行費用移至另一個執行緒。
- 這會轉移剖析成本,因此 UI 啟動速度可能會更快。這可能會縮短首次顯示內容所需時間,甚至互動時間,進而提高 Lighthouse 分數。
Web Worker 並不可怕,有了 Comlink 等工具,工作人員就能卸下重擔,成為各種網路應用程式的可行選擇。