图示:浏览器、主线程、工作者线程之间的关系和通信方式(附:ArrayBuffer 详解)

浏览器、主线程、工作者线程之间的关系和通信方式

一、基本概念

组件 说明
浏览器 宿主环境,提供运行网页的容器,包含多个进程(如渲染进程、网络进程、GPU 进程等)
主线程(Main Thread) 属于渲染进程,负责解析 HTML/CSS、执行 JavaScript、处理 DOM、响应用户交互等
工作者线程(Worker Thread) 由主线程创建,用于在后台执行 JavaScript(如 Web Worker),不能访问 DOM

二、通信机制:消息传递(Message Passing)

主线程与工作者线程之间 不能共享内存 ,只能通过 postMessage() + onmessage 异步传递消息(结构化克隆算法序列化数据)。


三、文本流程图


四、通信流程步骤(以 Web Worker 为例)

  1. 主线程 创建 Worker:

    Js

    javascript 复制代码
    const worker = new Worker('worker.js');
  2. 主线程 发送消息:

    Js

    javascript 复制代码
    worker.postMessage({ data: 'Hello Worker!' });
  3. 工作者线程worker.js)接收并处理:

    Js

    javascript 复制代码
    self.onmessage = (event) => {
    //heavyComputation() 并不是一个 JavaScript 内置函数,而是一个示意性的占位函数名,用来代表"耗时的计算任务"。
    //模拟在 Worker 线程中执行的、不适合放在主线程中的 CPU 密集型操作。
      const result = heavyComputation(event.data);
      self.postMessage(result); // 发回结果
    };
  4. 主线程 接收回传结果:

    Js

    javascript 复制代码
    worker.onmessage = (event) => {
      console.log('Result:', event.data);
    };
  5. 注意 :所有通信都是 异步、非阻塞 的,且数据被 复制(非引用)。


常见的 "Heavy Computation" 场景

类型 示例
数学计算 大数运算、矩阵乘法、加密/解密
数据处理 解析大型 JSON、CSV 转换、图像像素处理
算法执行 排序大量数据、路径搜索(如 A*)、模拟(物理/金融)
游戏逻辑 AI 决策、地图生成

为什么要在 Worker 中做这些?

因为 JavaScript 是单线程的(在主线程中)。如果在主线程执行耗时任务:

  • 页面会卡顿或冻结
  • 用户无法点击、滚动
  • 动画掉帧(jank)

而 Web Worker 提供了真正的多线程能力 (尽管通信受限),让这些任务在后台运行,不阻塞 UI

五、限制与注意事项

  • 可以传递:基本类型、对象(通过结构化克隆)、ArrayBuffer(可转移所有权)
  • 不能传递 :DOM 节点、函数、Error 对象、某些内置对象(如 window
  • 🔒 线程隔离 :Worker 无法访问 windowdocumentlocalStorage 等主线程全局对象
  • 🧩 多 Worker:一个页面可创建多个 Worker,彼此独立

六、扩展:其他类型的 Worker

类型 特点
Dedicated Worker 默认类型,仅创建它的主线程可通信
Shared Worker 多个 tab/iframe 可共享同一个 Worker
Service Worker 用于拦截网络请求、实现离线缓存(运行在独立线程,但生命周期由浏览器控制)

核心要点总结

  1. 线程隔离

    • 主线程:UI渲染、DOM操作、用户交互

    • Worker线程:纯计算、无DOM访问、独立全局作用域

  2. 通信方式

    • postMessage():结构化克隆,安全但有一定开销

    • MessageChannel:高效双向通信

    • SharedArrayBuffer:共享内存,零拷贝但需要同步

  3. 生命周期

    复制代码
    创建 → 通信 → 处理 → 响应 → [终止/重用]
  4. 适用场景

    • ✅ 图像/视频处理

    • ✅ 大数据计算

    • ✅ 复杂算法

    • ❌ DOM操作

    • ❌ 同步API调用

  5. Vue 3 集成

    • 使用 Composition API 封装 Worker 逻辑

    • 通过响应式数据同步状态

    • 注意组件卸载时的资源清理


图示

浏览器多线程架构示意图


通信流程图


线程与内存关系图


消息传递对比图


实际代码示例流程图


关键特点总结表



ArrayBuffer 是 JavaScript 中用于表示通用、固定长度的二进制数据缓冲区 的对象。它本身不能直接读写数据,而是作为底层内存的"容器",需要通过 视图(如 Uint8ArrayFloat32Array 等) 来访问其内容。

主线程与 Worker 线程通信 的上下文中,ArrayBuffer 有一个非常重要的特性:可以"转移"(transfer)所有权 ,而不是复制,从而实现零拷贝、高性能的数据传递


ArrayBuffer 详解

ArrayBuffer 是 JavaScript 中用于表示通用、固定长度的二进制数据缓冲区的对象。它本身不能直接读写数据,而是作为底层内存的"容器",需要通过 视图(如 Uint8ArrayFloat32Array 等) 来访问其内容。

主线程与 Worker 线程通信 的上下文中,ArrayBuffer 有一个非常重要的特性:可以"转移"(transfer)所有权 ,而不是复制,从而实现零拷贝、高性能的数据传递


🔍 1. 基本概念

javascript 复制代码
// 创建一个 1024 字节的 ArrayBuffer
const buffer = new ArrayBuffer(1024);

// 创建视图来读写数据
const uint8View = new Uint8Array(buffer);
uint8View[0] = 255; // 写入第一个字节
  • ArrayBuffer:原始二进制内存块(类似 C 的 malloc 分配的内存)
  • TypedArray(如 Uint8Array, Int32Array, Float64Array):提供类型化访问方式
  • DataView:提供更灵活的、可指定字节序的读写能力

🔄 2. 在主线程与 Worker 之间传递 ArrayBuffer

✅ 默认行为:结构化克隆(复制)

javascript 复制代码
// 主线程
const buffer = new ArrayBuffer(1024);
worker.postMessage(buffer); // ← 默认会复制整个 buffer
console.log(buffer.byteLength); // 仍然是 1024(因为是复制)

这种方式适合小数据,但大数据(如图像、音频)会因复制而性能低下。


⚡ 推荐方式:转移所有权(Transferable)

javascript 复制代码
// 主线程
const buffer = new ArrayBuffer(1024 * 1024); // 1MB
worker.postMessage(buffer, [buffer]); // ← 第二个参数:transferList
console.log(buffer.byteLength); // 输出 0!因为所有权已转移
javascript 复制代码
// worker.js
self.onmessage = (e) => {
  const receivedBuffer = e.data;
  console.log(receivedBuffer.byteLength); // 1048576(1MB)
  // 可以安全使用,无需复制
};

优势

  • 零内存拷贝

  • 极高效率(适用于音视频处理、WebGL、WASM 等场景)

  • 避免主线程卡顿
    注意

  • 转移后,原线程中的 ArrayBuffer 变为不可用byteLength === 0

  • 只能转移 ArrayBuffer 本身,不能转移 TypedArray(但可以转移其 .buffer


🧩 3. 实际应用场景

场景:图像像素处理

javascript 复制代码
// 主线程:从 canvas 获取像素数据
const imageData = ctx.getImageData(0, 0, width, height);
const buffer = imageData.data.buffer; // Uint8ClampedArray 的 buffer

// 转移到 Worker 处理(如滤镜)
worker.postMessage({ buffer, width, height }, [buffer]);

// 注意:imageData.data 现在已失效!
javascript 复制代码
// worker.js:处理像素
self.onmessage = (e) => {
  const { buffer, width, height } = e.data;
  const pixels = new Uint8ClampedArray(buffer);
  
  // 应用灰度滤镜
  for (let i = 0; i < pixels.length; i += 4) {
    const avg = (pixels[i] + pixels[i+1] + pixels[i+2]) / 3;
    pixels[i] = pixels[i+1] = pixels[i+2] = avg;
  }
  
  self.postMessage(buffer, [buffer]); // 处理完再转回主线程
};
javascript 复制代码
// 主线程接收处理后的图像
worker.onmessage = (e) => {
  const processedBuffer = e.data;
  const processedPixels = new Uint8ClampedArray(processedBuffer);
  const newImageData = new ImageData(processedPixels, width, height);
  ctx.putImageData(newImageData, 0, 0);
};

📌 总结

特性 说明
是什么 二进制数据的原始缓冲区
如何访问 通过 TypedArrayDataView
通信方式 可复制(默认)或转移(高效)
转移语法 postMessage(data, [arrayBuffer])
适用场景 音频/视频处理、图像操作、WASM 数据交换、大文件解析等

如果你正在处理大量二进制数据(比如从 fetch 获取的 arrayBuffer()FileReader 读取的文件、或 WebGL 纹理),务必考虑使用 transfer 模式,这是 Web Worker 高性能通信的关键技巧之一。

相关推荐
魂祈梦3 小时前
前端下载多个文件/浏览器批量下载文件
前端·浏览器
津津有味道5 小时前
读取NFC标签内的网址并打开网页
网址·浏览器·url·nfc·读卡器
鱼鱼块6 小时前
揭开浏览器底层的秘密:为什么一个 Chrome 能跑这么快又这么稳?
架构·操作系统·浏览器
Keely402851 天前
浏览器指纹识别:从原理到防护的完整指南
前端·浏览器
新晨4372 天前
跨域是服务器拒绝请求还是浏览器去拒绝的请求?
前端·浏览器
不一样的少年_4 天前
不仅免费,还开源?这个 AI Mock 神器我必须曝光它
前端·javascript·浏览器
JamesGosling6666 天前
深入理解内容安全策略(CSP):原理、作用与实践指南
前端·浏览器
月下点灯8 天前
🔄记住这张图,脑子跟着浏览器的事件循环(Event Loop)转起来了
前端·javascript·浏览器
用户7617363540111 天前
浏览器渲染原理
前端·浏览器