Web Worker 数据传递机制总结

113 阅读4分钟

一、postMessage() 传递数据的机制

1. 默认行为:结构化克隆(Structured Clone)

当我们用 worker.postMessage(data) 发送数据时,浏览器默认会:

  • 对数据进行结构化克隆(Structured Clone),即 深拷贝
  • 该拷贝包括对象、数组、Map、Set、TypedArray 等;
  • 不会复制原对象的引用关系;
  • 拷贝后的新对象在 Worker 中独立存在。

示例:

const obj = { a: 1, b: { c: 2 } };
worker.postMessage(obj);

Worker 收到的对象是一个全新的深拷贝。
修改其中的字段不会影响主线程的原对象。

2. 性能代价

结构化克隆会产生内存复制,数据越大性能损耗越高。

  • 小数据(几 KB 以内) → 几乎无感;
  • 中大型数据(几 MB 或更多) → 显著延迟;
  • 超大数据(图像、音视频缓冲、模型权重等) → 不可接受。

二、Transferable 对象与零拷贝机制

1. Transferable 是什么?

Transferable 是一种所有权转移(ownership transfer)机制: - 不复制数据,而是直接把内存使用权交给接收方; - 零拷贝(Zero-Copy); - 传递后原对象在发送端会变为 "detached"(不可用)。

2. 可被 Transfer 的类型包括:

  • ArrayBuffer
  • MessagePort
  • OffscreenCanvas
  • 部分 WebAssembly 内存对象

3. 使用方式

postMessage() 的第二个参数用于指定可转移对象数组:

worker.postMessage(buffer, [buffer]);

等价于: > 把 buffer 这个 ArrayBuffer 的所有权转移到 Worker。


三、Transferable 与深拷贝的对比

对比项结构化克隆(默认)Transferable(零拷贝)
是否复制内存✅ 会(深拷贝)❌ 不会(转移所有权)
性能慢(取决于数据量)快(常数级)
发送后可用性原数据仍可访问原数据变为 detached
数据安全性双方各有副本单一所有权
适合场景小型任务、简单通信大数据(如视频帧、音频流、模型推理)

四、从 Worker 传回主线程:可否也用 Transferable?

可以,而且非常常见。

Worker 可以像主线程一样,用 postMessage() 的第二个参数将数据"零拷贝"传回:

// worker.js
self.onmessage = e => {
  const buffer = e.data;
  const view = new Uint8Array(buffer);
  for (let i = 0; i < view.length; i++) {
    view[i] *= 2; // 数据处理
  }

  // ✅ Transferable 回主线程
  self.postMessage(buffer, [buffer]);
};

主线程:

worker.onmessage = e => {
  const view = new Uint8Array(e.data);
  console.log(view); // [2,4,6,8]
};

效果: - 零拷贝往返; - 所有权被从主线程 → Worker → 主线程; - 性能几乎为原生内存操作。


五、Transferable 的工作原理图

主线程                  Worker
  │                        │
  │  postMessage(buffer,[buffer]) ─────► 所有权转移
  │                        │  buffer 可用
  │  buffer → detached     │
  │                        │ 处理数据
  │◄──── postMessage(buffer,[buffer])  所有权转回
  │  buffer 可用            │  buffer → detached

🔁 所有权在两个线程间来回"传递",而不是复制。


六、可以反复来回传递(循环复用)

高性能场景(如实时数据流)通常使用内存循环复用: - 只创建一次 ArrayBuffer; - 反复在主线程与 Worker 间转移所有权; - 避免反复分配和 GC 压力。

示例:

// main.js
let buffer = new ArrayBuffer(4096);
const worker = new Worker('worker.js');

function loop() {
  worker.postMessage(buffer, [buffer]);
}

worker.onmessage = e => {
  buffer = e.data; // 拿回所有权
  console.log('processed', new Uint8Array(buffer)[0]);
  loop(); // 再次传回 Worker
};

loop();
// worker.js
self.onmessage = e => {
  const view = new Uint8Array(e.data);
  for (let i = 0; i < view.length; i++) view[i]++;
  self.postMessage(view.buffer, [view.buffer]);
};

✅ 极高性能,无拷贝、可复用、适合流式任务。


七、总结核心问答

你的问题答案总结
🟦 postMessage 默认是深拷贝还是浅拷贝?默认使用 结构化克隆算法(深拷贝)
🟦 深拷贝是否自动?需要自己实现吗?自动进行,不可关闭
🟦 Transferable 怎么用?postMessage() 第二个参数中传数组:[buffer]
🟦 用 Transferable 有没有劣势?原数据会被 “detached”,不能再用(所有权被转移)
🟦 Worker 处理完后能用 Transferable 发回主线程吗?✅ 完全可以,也是推荐做法
🟦 零拷贝能往返循环复用吗?✅ 能,适合高性能任务(音视频、AI、图像处理)

八、关键术语复习

术语含义
结构化克隆(Structured Clone)浏览器的深拷贝算法,用于跨线程传值
Transferable一种允许“零拷贝”的内存所有权转移机制
detached buffer被转移所有权后失效的 ArrayBuffer
zero-copy直接转移内存而不复制的高性能通信方式
ownership transferTransferable 的核心机制,本质是资源交接

九、实践建议

场景推荐方式
小数据(简单通信)默认 postMessage(data)
大数据(图像/音频流)使用 Transferable
往返计算(高频任务)使用循环 Transferable
多线程共享数据考虑 SharedArrayBuffer(共享内存,不转移)

✨ 十、总结一句话

Web Worker 默认深拷贝传值,但 Transferable 提供了零拷贝内存转移。
Transferable 可双向使用,实现高性能"主线程 ↔ Worker"数据流循环。