JS中的多線程——Web Worker

眾所周知,JavaScript 是單線程運行的(至于為什么是單線程可以看一下這篇文章——事件循環機制),當瀏覽器主線程被大量計算任務阻塞時,頁面就會出現明顯的卡頓現象。Web Worker 提供了在獨立線程中運行 JavaScript 的能力,通過消息傳遞或共享內存(SharedArrayBuffer)與主線程協作。下面通過這篇文章向大家介紹webworker是什么以及如何使用,以及具體開發中的使用場景


一、概念與由來

1. 什么是 Web Worker?

Web Worker 是瀏覽器暴露的一個后臺執行環境——它在獨立線程中運行 JavaScript,上下文沒有 window、無法直接訪問 DOM。主線程與 Worker 通過 postMessage/onmessage 通信。默認通信使用結構化克隆(復制數據),也支持 Transferable(零拷貝的所有權轉移)和 SharedArrayBuffer(共享內存 + Atomics 同步)。

2. 為什么要它?它能解決什么問題?

瀏覽器的主線程(通常也稱為 UI 線程)是一個極其繁忙的“單線程序員”,它肩負著腳本執行、樣式計算、布局(重排)、繪制(重繪)以及處理用戶事件(如點擊、滾動)等多重重任。任何長時間運行的 JavaScript 任務都會阻塞這個線程,導致頁面無法及時更新和響應用戶交互,從而造成令人不快的“卡頓”現象(如按鈕點擊無反應、動畫掉幀)。

要理解問題的根源,我們可以從計算機任務的類型劃分入手:

I/O 密集型(I/O-bound):指任務大部分時間在等待輸入/輸出操作完成,例如網絡請求(AJAX/Fetch)或文件讀取。這類任務通常通過異步回調(如 Promise, async/await)來處理,瀏覽器在等待期間可以騰出主線程做其他事情。

CPU 密集型(CPU-bound):指任務需要進行大量復雜的計算,持續占用中央處理器(CPU)資源直到計算完成。例如,大規模數據的排序或篩選、復雜的數學計算(如加密解密、圖像/視頻處理、物理模擬)、語法高亮或代碼編譯等。異步編程模型無法解決CPU密集型任務帶來的阻塞問題,因為計算本身就在主線程上,必須算完才能繼續。

Web Worker 的核心目標,正是為了攻克 CPU 密集型任務 帶來的阻塞難題。它允許開發者創建一個獨立于主線程的后臺線程,將那些“重活累活”(CPU密集型任務)丟進去執行。兩個線程并行不悖,主線程因此得以保持流暢,及時響應用戶交互和更新UI,從而從根本上提升了前端應用的性能和用戶體驗。

3. 核心設計取舍(對工程意味著什么)

  • 線程隔離(安全):避免競態條件和復雜的 DOM 線程安全問題,但代價是:Worker 不能直接操控 DOM。
  • 消息傳遞優先(簡單):默認復制數據簡單安全,但會產生拷貝開銷;為此瀏覽器提供 Transferable/SharedArrayBuffer。
  • 創建成本(有限資源):線程不是免費的——創建、銷毀、內存占用都要考慮,因此生產環境通常要復用(池化)Worker。

理解這些取舍能幫助你判斷什么時候應該用 Worker、怎么設計任務拆分、以及如何在工程中平衡性能與復雜度。


二、基礎使用

下面給出通過一個基礎的示例來看webworker的基礎用法。

1. 簡單示例(文件式 Worker)

worker.js

// worker.js
self.onmessage = (e) => {const n = e.data;// 計算密集型:斐波那契(示例,真實項目請替換為合適算法)function fib(x){ return x <= 1 ? x : fib(x-1) + fib(x-2); }const r = fib(n);self.postMessage(r);
};

main.js

const w = new Worker('worker.js'); // 可加 { type: 'module' } 使用 ES module
w.onmessage = (e) => console.log('結果:', e.data);
w.postMessage(40);

對一些關鍵 API的解釋

  • new Worker(url, options):創建 Worker。options.type = 'module' 支持 import/export
  • worker.postMessage(value, transferables?):發送消息。第二個參數可傳 Transferable(如 ArrayBuffer)做零拷貝。
  • worker.onmessage / self.onmessage:接收消息。
  • worker.terminate() / self.close():銷毀 Worker(釋放線程與資源)。
  • SharedArrayBuffer + Atomics:共享內存與同步(復雜場景用),需滿足安全頭。

3. Transferable(零拷貝)示例

當你傳輸大數組或位圖時,復制開銷很昂貴。使用 Transferable 把所有權轉移給 Worker:

const ab = new ArrayBuffer(1024*1024);
worker.postMessage(ab, [ab]); // 發送后 main 端 ab 變為 neutered(不可訪問)

4. 簡易 Promise 化調用(一次性任務)

function runTask(script, payload) {return new Promise((resolve, reject) => {const w = new Worker(script);w.onmessage = e => { resolve(e.data); w.terminate(); };w.onerror = e => { reject(e); w.terminate(); };w.postMessage(payload);});
}

三、進階實戰

Web Worker 的強大不言而喻,但在大型工程中,粗暴地創建和使用 Worker 反而會帶來管理混亂和性能問題。本章節將深入探討如何在工程中優雅、高效、安全地使用 Worker。

1. 何時使用 Worker(冷靜判斷)

  • 適合:任務明顯耗時,且會影響幀率或用戶交互(圖像處理、大數據解析、音視頻編/解碼、加密/壓縮、機器學習推理),這些操作都有一個前提,那就是不涉及DOM操作,因為Web Worker不可以操作DOM。
  • 不適合:非常短小且頻繁的任務(Worker 創建/消息開銷可能高于任務本身)、純 DOM 操作(Worker 無法訪問 DOM)。

簡單來說,不與DOM打交道、計算量大、耗時長的任務,就是Worker的完美候選者。

2. Worker 池(生產級必備)

池化的必要性: 線程的創建和銷毀會產生性能開銷,同時考慮到設備CPU核心數有限的實際情況。線程池通過復用現有線程、控制并發數量以及支持超時/重試等策略來優化資源使用。

Worker池的實現原理: 維護一組預先初始化的空閑Worker實例。任務到達時,從池中分配可用Worker執行任務,任務完成后Worker自動回歸線程池。這種機制有效避免了頻繁實例化帶來的資源消耗。

下面是一段代碼示例,讓我們來看看具體的操作方式,看他是如何實現Worker池的。

// WorkerPool.js
class WorkerPool {constructor(workerScript, poolSize = navigator.hardwareConcurrency || 4) {this.workerScript = workerScript;this.poolSize = poolSize;this.workers = []; // 空閑Worker隊列this.queue = []; // 任務等待隊列 { resolve, reject, message, transfer }// 初始化Worker池for (let i = 0; i < poolSize; i++) {this._createWorker();}}_createWorker() {const worker = new Worker(this.workerScript);worker.onmessage = (e) => {// 當前Worker完成任務,從隊列頭取一個任務給它const nextTask = this.queue.shift();if (nextTask) {const { message, transfer, resolve, reject } = nextTask;worker.postMessage(message, transfer);// 將resolve和reject重新掛載到worker對象上,用于下一次onmessageworker._resolve = resolve;worker._reject = reject;} else {// 沒有任務了,將此Worker放入空閑隊列this.workers.push(worker);}// 外部Promise的resolveworker._resolve(e.data);};worker.onerror = (e) => {if (worker._reject) {worker._reject(e);worker._resolve = null;worker._reject = null;}// Worker出錯,可能需要銷毀并創建一個新的替換this._replaceWorker(worker);};// 初始創建后是空閑的this.workers.push(worker);}postMessage(message, transfer = []) {// 如果有空閑Worker,直接使用if (this.workers.length > 0) {const worker = this.workers.pop();// 返回一個Promise,將resolve/reject方法暫存在worker對象上return new Promise((resolve, reject) => {worker._resolve = resolve;worker._reject = reject;worker.postMessage(message, transfer);});} else {// 沒有空閑Worker,將任務加入隊列等待return new Promise((resolve, reject) => {this.queue.push({ resolve, reject, message, transfer });});}}_replaceWorker(badWorker) {// 從池中移除壞的Workerconst index = this.workers.indexOf(badWorker);if (index !== -1) this.workers.splice(index, 1);badWorker.terminate();// 創建一個新的Worker補充池子this._createWorker();}terminateAll() {this.workers.forEach(worker => worker.terminate());this.workers = [];this.queue = [];}
}// 使用示例
const myWorkerPool = new WorkerPool('worker-script.js', 4);// 異步提交任務并等待結果
async function processData(data) {try {const result = await myWorkerPool.postMessage(data);console.log('Result from worker:', result);} catch (error) {console.error('Worker error:', error);}
}

簡單總結一下,該 WorkerPool 通過維護固定數量的 Worker 實例,實現了任務的高效調度:空閑 Worker 直接處理任務,繁忙時任務排隊等待,Worker 出錯時自動替換,最終達到減少 Worker 創建開銷、提高并發處理效率的目的。外部通過 Promise 接口即可異步獲取任務結果,使用簡單且不阻塞主線程。

建議從以下方面進行優化:超時控制、健康檢查、優先級隊列以及任務取消機制(可考慮采用 token 方案)。這些內容不在本文討論范圍內,就先不展開說明了,后面有機會再詳細介紹。

3. 圖像處理與Canvas(OffscreenCanvas)

傳統痛點: 在Worker中處理圖像數據,需要將 ImageData 來回傳遞,仍然可能阻塞主線程(雖然后臺計算時UI不卡,但傳輸和最終繪制可能卡)。

現代解決方案: OffscreenCanvas。它允許你將一個Canvas的控制權完全轉移給Worker,從計算到渲染全過程都在后臺進行,主線程幾乎零開銷。

下面通過代碼來看如何具體使用

在主線程中:

const offscreenCanvas = document.querySelector('canvas').transferControlToOffscreen();
worker.postMessage({ canvas: offscreenCanvas }, [offscreenCanvas]);
// 注意:第二個參數必須傳輸Transferable對象

在Worker中:

onmessage = function(e) {const canvas = e.data.canvas;const ctx = canvas.getContext('2d');// 現在你可以在Worker中直接進行繪圖操作!ctx.beginPath();ctx.moveTo(10, 10);ctx.lineTo(100, 100);ctx.stroke();// 或者處理圖像const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);// ... 對imageData進行復雜的像素操作 ...ctx.putImageData(imageData, 0, 0);
};

注意: 瀏覽器兼容性是主要考量,但現代瀏覽器已廣泛支持。

4. SharedArrayBuffer 場景(并發協作,需注意安全)

SharedArrayBuffer 允許多線程共享內存并配合 Atomics 做同步,適合低延遲、多 Worker 協作(例如分塊矩陣乘法、并行 FFT)。但必須滿足安全要求(瀏覽器會在沒有 COOP/COEP 的情況下禁用它)。

必需的 HTTP 響應頭(服務器上設置):

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

示例(主/Worker 端簡化)

// 主線程
const sab = new SharedArrayBuffer(4); // 4 bytes
const arr = new Int32Array(sab);
arr[0] = 0;
worker.postMessage({ sab });// Worker
self.onmessage = (e) => {const arr = new Int32Array(e.data.sab);// 等待通知Atomics.wait(arr, 0, 0);// 繼續工作...
};

注意:使用 SharedArrayBuffer 時要小心死鎖、復雜同步與性能調優(頻繁 Atomics 操作也會帶來開銷)。

5. 錯誤處理、生命周期管理與降級策略

  • 錯誤上報worker.onerror + Worker 內捕獲異常并 postMessage 反饋結構化錯誤,便于上報與診斷。
  • 超時與終止:對每個任務設置超時;超時后 terminate() 并重試或降級回主線程實現。
  • 銷毀時機:組件卸載、頁面隱藏、beforeunload 時清理 Worker;池實現中做“空閑銷毀”(空閑超過某時長自動銷毀部分 Worker)。
  • 降級處理:若環境不支持某特性(SharedArrayBuffer、OffscreenCanvas、module worker),提供主線程回退實現或分片處理以保證功能可用但性能降級可控。

6. 性能測量(判斷是否值得起 Worker)

別憑感覺決定:量化!

  1. 在主線程測量原始任務耗時(performance.now())。
  2. 在 Worker 方案中測量序列化、傳輸、執行與回傳時間(在主/Worker 端分別打點)。
  3. 在目標設備(PC/中低端手機)上比對用戶可感知延遲(例如輸入響應時間、動畫掉幀)與資源占用(CPU/內存)。
    只有當總體體驗有明顯改善時,才長期采用。

7. 兼容性與部署注意

  • type: 'module' Worker 在現代瀏覽器支持良好,但舊瀏覽器可能不支持,需做降級。常見的瀏覽器對webworker的支持情況如下圖所示:
    web worker支持情況

  • OffscreenCanvasImageBitmapSharedArrayBuffer 的支持度差異更大,使用前要 feature-detect 并提供回退。下面貼出來常見的瀏覽中這三者的支持情況供大家參考。
    OffscreenCanvas
    ImageBitmap
    ![SharedArrayBuffer![](https://i-blog.csdnimg.cn/direct/8a89fae121c442e592dc6154da60d98e.png)

  • 如果要在生產環境啟用 SharedArrayBuffer,務必在服務器端配置 COOP/COEP,并測試第三方嵌入對策略的影響(某些第三方資源可能需要 crossorigin / require-corp 的處理)。


四、實戰清單

  • 先測量:確認任務確實會卡住主線程。
  • 優化算法:先優化算法/批處理,再考慮 Worker。
  • 池化優先:為短任務或頻繁任務實現 Worker 池,控制并發與復用。
  • 使用 Transferable:傳二進制或 ImageBitmap 時優先 Transferable。
  • OffscreenCanvas:圖像/Canvas 繪制在 Worker 內做(若瀏覽器支持)。
  • SharedArrayBuffer:只在確需低延遲協作且能配置 COOP/COEP 時使用。
  • 錯誤/超時/回退:實現報錯上報、超時終止與主線程回退策略。
  • 在真實目標設備上做對比測試:PC、Android、iOS 都測一次。

結語

Web Worker 是移動/桌面 Web 中提升用戶體驗的重要武器,但它不是萬金油:先優化、再并行、再復用。將任務合理拆分、用池化與 Transferable/OffscreenCanvas/SharedArrayBuffer 等技術配合,你就能把耗時任務交給“后臺工人”,讓主線程專注做界面,整體體驗穩而順。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/96460.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/96460.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/96460.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【SQL注入】延時盲注

sleep(n)??: 核心延時函數。使數據庫程序暫停 n秒。??if(condition, true_expr, false_expr)??: 條件判斷函數。如果 condition為真&#xff0c;執行 true_expr&#xff0c;否則執行 false_expr。??用于將延時與判斷條件綁定??。??mid(a, b, c)??: 字符串截取函數…

IntelliJ IDEA 2025.1 Java Stream Debugger 快速使用指南

1. 功能概覽 Java Stream Debugger 提供 Trace Current Stream Chain 功能&#xff0c;用來在調試時分析和可視化 Stream 操作鏈。 主要用途&#xff1a; 在運行時查看流操作鏈的每一步輸出找出 map/filter 等操作的問題避免手動加 peek() 打印調試2. 使用入口 在 IDEA 2025.1 …

ARM-指令集全解析:從基礎到高階應用

一、ARM 指令集體系結構版本ARM 公司定義了多個指令集版本&#xff1a;ARMv1&#xff1a;原型機 ARM1&#xff0c;沒有用于商業產品。ARMv2&#xff1a;擴展 V1&#xff0c;包含 32 位乘法指令和協處理器指令。ARMv3&#xff1a;第一個微處理器 ARM6 核心&#xff0c;支持 Cach…

第3講 機器學習入門指南

近年來&#xff0c;隨著企業和個人生成的數據量呈指數級增長&#xff0c;機器學習已成為日益重要的技術領域。從自動駕駛汽車到流媒體平臺的個性化推薦&#xff0c;機器學習算法已廣泛應用于各個場景。讓我們深入解析機器學習的核心要義。3.1 機器學習定義機器學習是人工智能的…

深入理解跳表:多層索引加速查找的經典實現

跳表&#xff08;Skip List&#xff09;是一種多層有序鏈表結構&#xff0c;通過引入多級索引加速查找&#xff0c;其核心設計類似于“立體高速公路系統”&#xff0c;底層是原始鏈表&#xff0c;上面有各種高度的"高架橋"。 高層道路跨度大&#xff0c;連接遠方節點…

Flutter 視頻播放器——flick_video_player 介紹與使用

在移動端應用中&#xff0c;視頻播放是一個常見的功能場景&#xff0c;例如短視頻、直播、課程、廣告展示等。 Flutter 本身并沒有直接提供視頻播放器組件&#xff0c;而是依賴第三方庫來實現。 今天要介紹的庫是 flick_video_player&#xff0c;它基于 video_player 封裝&…

編寫cmakelists文件常用語句

cmake_minimum_required (VERSION 3.10) 指定最小版本project(XXXX) 指定項目名字 ---------------set(MAIN_EXEC_NAME dwarf_parser) 定義變量${ MAIN_EXEC_NAME } 變量取值set(CMAKE_CXX_STANDARD 14) 指定c14標準&#xff0c;還有11、17、20等標準…

麒麟桌面系統找不到mbr啟動,并重新安裝grub

根據你提供的情況,“麒麟桌面系統找不到MBR啟動”,這通常是由于GRUB引導損壞、MBR記錄丟失或分區表異常導致的。你可以按照以下步驟重新安裝GRUB并修復MBR啟動: ? 步驟一:準備工具 使用銀河麒麟LiveCD或U盤啟動盤(可用Ventoy制作); 啟動電腦,選擇從U盤或光盤進入Live環…

【音頻字幕】構建一個離線視頻字幕生成系統:使用 WhisperX 和 Faster-Whisper 的 Python 實現

一、背景介紹 對于一端沒有字幕外國視頻、字幕&#xff0c;在不懂外語的情況下&#xff0c;怎么獲取相關內容&#xff1f;作為技術宅&#xff0c;怎么自建搭建一個語音轉文字的環境當前AI技術這么發達&#xff1f; 試試 二、系統設計 音頻提取(僅僅是視頻需要該邏輯、本身就是音…

Linux ALSA架構:PCM_OPEN流程 (二)

一 應用端源碼路徑: external\tinyalsa\pcm.c external\tinyalsa\pcm_hw.cstruct pcm *pcm_open(unsigned int card, unsigned int device,unsigned int flags, struct pcm_config *config) {...pcm->ops &hw_ops;pcm->fd pcm->ops->open(card, device,…

tp5的tbmember表閉包查詢 openid=‘abc‘ 并且(wx_unionid=null或者wx_unionid=‘‘)

閉包查詢 tbmember表閉包查詢查詢 openid‘abc并且islose0并且islogout0并且&#xff08;wx_unionidnull或者wx_unionid’&#xff09; Db::table(tbmember)->where([openid>abc,islose>0,islogout>0])->where(function ($query){$query->where(wx_unioni…

邪修實戰系列(3)

1、第一階段邪修實戰總覽&#xff08;9.1-9.30&#xff09; 把第一階段&#xff08;基礎夯實期&#xff09;的學習計劃拆解成極具操作性的每日行動方案。這個計劃充分利用我“在職學習”的特殊優勢&#xff0c;強調“用輸出倒逼輸入”&#xff0c;確保每一分鐘的學習都直接服務…

【GD32】ROM Bootloader、自定義Bootloader區別

Bootloader是應用程序跑起來之前&#xff0c;用于初始化的一段程序&#xff0c;它分為兩種&#xff0c;ROM Bootloader、自定義Bootloader。GD32芯片出廠時預燒錄在ROM中的Bootloader&#xff08;以下簡稱ROM Bootloader&#xff09;和自己編寫的Bootloader&#xff08;以下簡稱…

Linux防火墻-Firewalld

一、 概述 按表現形式劃分&#xff1a; 軟件防火墻&#xff1a; 集成在系統內部&#xff0c;Linux系統&#xff1a; iptables、firewalld、ufw&#xff1b; windows系統下&#xff1a; windows defender 硬件防火墻&#xff1a; 華為防火墻、思科防火墻、奇安信防火墻、深信服防…

【Qt】PyQt、原生QT、PySide6三者的多方面比較

目錄 引言 一、基本定義 二、核心對比維度 1. 編程語言與開發效率 2. 功能與 API 兼容性 3. 性能表現 4. 許可證與商業使用 5. 社區與文檔支持 三、遷移與兼容性 四、適用場景推薦 五、總結對比表 總結 引言 PySide6、PyQt&#xff08;通常指 PyQt5/PyQt6&#xf…

JavaWeb站內信系統 - 技術設計文檔

1. 系統概述1.1 項目背景本系統旨在為企業或社區平臺提供一套完整的站內信解決方案&#xff0c;支持用戶之間的消息發送、接收、管理等功能&#xff0c;提升用戶間的溝通效率。1.2 設計目標實現用戶間消息發送和接收支持一對一和一對多消息發送提供消息狀態跟蹤&#xff08;已讀…

Java基礎 9.10

1.System類常見方法和案例exit&#xff1a;退出當前程序arraycopy&#xff1a;復制數組元素&#xff0c;比較適合底層調用&#xff0c;一般使用 Arrays.copyOf 完成復制數組int[] src{1,2,3};int[] dest new int[3]; System.arraycopy(src, 0, dest, 0, 3);currentTimeMilens&…

詳解flink性能優化

1. 簡介 Apache Flink是一個強大的流處理框架&#xff0c;其性能很大程度上取決于內存的使用效率。在大規模數據處理場景中&#xff0c;合理的內存配置和優化可以顯著提升Flink作業的性能和穩定性。本文將深入探討Flink內存優化的各個方面&#xff0c;包括狀態后端選擇、內存配…

VueFlow的箭頭怎么調整

正好最近用到了VueFlow組件&#xff0c;發現箭頭默認樣式太小&#xff0c;無法體現流程展示&#xff0c;因此翻閱相關資料得出下列方法&#xff0c;有什么更好的方法&#xff0c;大家可以推薦推薦&#xff0c;謝謝。方法1&#xff1a;通過邊&#xff08;Edge&#xff09;的樣式…

【Python】S1 基礎篇 P9 文件處理與異常處理技術

目錄文件讀取操作讀取文件的全部內容相對路徑和絕對路徑逐行訪問文件內容文件寫入操作寫入單行內容寫入多行內容結構化數據的存儲異常處理機制理解異常的工作原理ZeroDivisionError異常示例try-except語句塊的使用else語句塊的正確使用靜默失敗的合理應用本文將深入探討Python中…