主執行緒架構可以大幅改善應用程式的可靠性和使用者體驗。
在過去 20 年間,網路從只有幾種樣式和圖片的靜態文件,演變為複雜的動態應用程式。不過,有一個部分幾乎沒有改變:每個瀏覽器分頁 (有些例外情況) 只有一個執行緒,用於轉譯網站和執行 JavaScript。
因此,主執行緒已嚴重超載。隨著網頁應用程式變得越來越複雜,主要執行緒就會成為效能的重要瓶頸。更糟的是,在主執行緒上為特定使用者執行程式碼所需的時間幾乎完全無法預測,因為裝置功能會對效能造成巨大影響。隨著使用者透過越來越多樣化的裝置存取網際網路,從功能極為受限的功能型手機,到效能強大、刷新率高的旗艦機種,這種不可預測性只會越來越嚴重。
如果我們希望複雜的網頁應用程式可穩定地符合效能指南 (例如根據人類感知和心理學的經驗資料制定的 Core Web Vitals),就需要找到方法,在主執行緒 (OMT) 之外執行程式碼。
為何選擇 Web Worker?
根據預設,JavaScript 是單一執行緒語言,會在主執行緒上執行工作。不過,Web Workers 可讓開發人員建立單獨的執行緒,以便處理主執行緒以外的工作,這類似於主執行緒的逃生門。雖然網頁工作者的範圍有限,且無法直接存取 DOM,但如果需要執行大量工作而主執行緒無法負荷,網頁工作者就會非常有用。
在Core Web Vitals 方面,從主執行緒中移出工作可能會有所幫助。特別是,將工作從主執行緒卸載至網路 worker,可以減少主執行緒的競爭,進而改善頁面的 Interaction to Next Paint (INP) 回應速度指標。當主執行緒的工作量減少時,就能更快速地回應使用者互動。
較不重要的主執行緒工作 (尤其是在啟動期間),也對最大內容繪製 (LCP) 可能因為減少長時間工作而受益。算繪 LCP 元素需要主執行緒時間,無論是算繪文字或圖片 (這兩者都是常見的 LCP 元素),都需要主執行緒時間。因此,您可以減少整體主執行緒的工作量,確保網頁的 LCP 元素不太可能遭到耗時工作阻斷,而這些耗時工作可由網頁工作代管。
網路工作站的執行緒
其他平台通常會透過允許您為執行緒提供函式,以便與程式的其他部分平行執行,來支援平行工作。您可以從兩個執行緒存取相同的變數,而且這些共用資源的存取權可與互斥鎖和信號量同步,以防競爭狀況發生。
在 JavaScript 中,我們可以透過網路工作者取得大致類似的功能,這些功能自 2007 年推出以來,自 2012 年起已獲得所有主要瀏覽器的支援。Web worker 會與主執行緒並行執行,但與作業系統執行緒不同,Web worker 無法共用變數。
如要建立網頁工作站,請將檔案傳遞至 worker 建構函式,以便在個別執行緒中開始執行該檔案:
const worker = new Worker("./worker.js");
使用 postMessage
API 傳送訊息,與網路工作人員通訊。在 postMessage
呼叫中將訊息值做為參數傳遞,然後在 worker 中新增訊息事件事件監聽器:
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
// ...
});
如要將訊息傳回至主執行緒,請在網路 worker 中使用相同的 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 是一種程式庫,可讓您使用網路 worker,而無須考慮 postMessage
的詳細資料。Comlink 可讓您在網路工作站和主執行緒之間共用變數,就像其他支援執行緒作業的程式設計語言一樣。
設定 Comlink 的方式如下:將 Comlink 匯入網路工作站,並定義一組要公開給主執行緒的函式。接著,您可以在主執行緒上匯入 Comlink、包裝 worker,並取得對公開函式的存取權:
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
變數與網路工作者中的變數行為相同,唯一的差別是每個函式都會傳回值的承諾,而非值本身。
您應該將哪些程式碼移至網頁工作站?
網路 worker 無法存取 DOM 和許多 API (例如 WebUSB、WebRTC 或 Web Audio),因此您無法將依賴這類存取權的應用程式部分放入 worker。不過,將每個小程式碼移至 worker 後,主要執行緒就能為必須在該處執行的作業 (例如更新使用者介面) 提供更多空間。
網頁開發人員遇到的問題是,大多數網頁應用程式都會依賴 Vue 或 React 等 UI 架構,用於協調應用程式中的所有內容;所有內容都是架構的元件,因此與 DOM 有內在關聯。這似乎會導致難以遷移至 OMT 架構。
不過,如果我們改用模型,將 UI 疑慮與其他問題 (例如狀態管理) 區隔開來,即使採用架構式應用程式,網路工作處理工作也能派上用場。這正是 PROXX 採用的方法。
PROXX:OMT 個案研究
Google Chrome 團隊開發的 PROXX 是符合漸進式網頁應用程式規定的「地雷尋寶」克隆版,包括可離線運作及提供吸引人的使用者體驗。不過,早期版本的遊戲在功能手機等受限裝置上表現不佳,因此團隊發現主要執行緒是瓶頸。
團隊決定使用 Web Workers,將遊戲的視覺狀態與邏輯分開:
- 主執行緒會處理動畫和轉場的轉譯作業。
- Web worker 會處理純粹運算的遊戲邏輯。
OMT 對 PROXX 的功能手機效能帶來有趣的影響,在非 OMT 版本中,使用者與 UI 互動後,UI 會凍結六秒。系統不會提供任何回饋,使用者必須等待完整的六秒鐘,才能執行其他操作。
不過,在 OMT 版本中,遊戲需要 十二 秒才能完成 UI 更新。這似乎導致效能下降,但實際上會讓使用者獲得更多回饋。發生速度變慢的情形,是因為應用程式傳送的幀數比非 OMT 版本多,後者根本沒有傳送任何幀。因此,使用者會知道發生了什麼事,並可在 UI 更新時繼續玩遊戲,讓遊戲體驗大幅提升。
這是我們有意做出的取捨:我們為受限裝置的使用者提供「更佳」的體驗,同時不會對高階裝置的使用者造成懲罰。
OMT 架構的影響
如 PROXX 範例所示,OMT 可讓應用程式在更多裝置上穩定執行,但不會加快應用程式的速度:
- 您只是將工作從主執行緒移出,並未減少工作量。
- 網路輔助程式和主執行緒之間的額外通訊開銷有時可能會讓速度略微下降。
考量的優缺點
由於主執行緒可在 JavaScript 執行期間自由處理使用者互動作業 (例如捲動畫面),因此即使總等待時間可能會略微延長,但遺漏的畫面會減少。讓使用者稍微等待一下,比丟棄影格更為理想,因為丟棄影格發生的時間很短,而使用者感知等待時間之前,有 數百 毫秒的時間。
由於不同裝置的效能無法預測,因此 OMT 架構的目標其實是降低風險,也就是讓應用程式在面臨變化多端的執行階段條件時更為穩健,而非追求並行化的效能優勢。隨著韌性和使用者體驗的提升,任何在速度上都有小權衡取捨。
工具相關注意事項
網路工作者尚未成為主流技術,因此大多數模組工具 (例如 webpack 和 Rollup) 並未預設支援網路工作者。(不過 Parcel 會執行這項操作!)幸好,有外掛程式可讓網路工作站與 webpack 和 Rollup 搭配運作:
- webpack 適用的worker-plugin
- Rollup 的 rollup-plugin-off-main-thread
總結
為了確保應用程式盡可能可靠且易於存取,尤其是在日益全球化的市場中,我們需要支援受限裝置,因為全球大多數使用者都是透過這類裝置存取網際網路。OMT 提供可行的方法,可在不影響高階裝置使用者的情況下,提升這類裝置的效能。
此外,OMT 還有其他好處:
- 將 JavaScript 執行成本移至另一個執行緒。
- 這種方式會變動「剖析」成本,這表示 UI 的啟動速度可能會更快。這樣做可能會縮短首次顯示內容所需時間,甚至互動時間,進而提高 Lighthouse 分數。
使用 Web worker 不必感到害怕。Comlink 等工具可協助員工完成工作,並為各種網路應用程式提供可行選擇。