当前位置:首页 > 文章列表 > 文章 > 前端 > Web Worker 怎样转移 ArrayBuffer 避免复制

Web Worker 怎样转移 ArrayBuffer 避免复制

来源:17golang原创 2026-10-15 06:20:47 0浏览 收藏

我第一次把几十 MB 的二进制数据交给 Web Worker 做解析时,页面卡顿并没有出现在 Worker 的计算逻辑里,而是出现在消息交接处。普通 postMessage() 不是把同一个对象“递过去”,而是通过 structured clone 在另一侧得到副本;数据越大,复制和垃圾回收的压力越明显。

官方地址:https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects

如果这块内存只需要被 Worker 接管,就把 ArrayBuffer 同时放进消息和 transfer list:worker.postMessage({ buffer }, [buffer])。转移完成后,主线程原来的 buffer 会变成 detached 状态,不能再继续读写;这正是“移动所有权”而不是“复制数据”。

我先确认:问题确实出在消息复制

我把问题收窄到一个可判断的边界:主线程拥有一个 ArrayBuffer,Worker 需要独占这块资源完成处理,主线程在交接后不再使用原引用。典型负载包括文件分片、二进制协议解析、音视频帧预处理和大块压缩输入。

如果两边都要继续访问同一份数据,转移就不合适;如果数据很小,structured clone 的简单性可能比所有权管理更有价值。转移的收益来自省掉大块内存的复制,但代价是发送端必须接受旧引用失效。

方案内存语义适合场景主要代价
普通 postMessage(data)structured clone,接收侧得到副本小对象、两侧都需要独立副本大块二进制会产生复制成本
postMessage(data, [buffer])ArrayBuffer 所有权转给接收侧单向消费、独占处理、文件或帧数据发送侧 buffer detached,不能复用
共享内存多个上下文访问同一资源需要并发共享的特殊场景同步、隔离和安全约束更复杂
主线程、payload、ArrayBuffer、transfer list、Worker 和 detached buffer 的静态所有权关系说明图
图1:这是主线程与 Worker 之间的静态所有权说明图;transfer list 负责声明资源转移,发送端旧 buffer 进入 detached 状态,不代表运行截图。

最小写法:把 ArrayBuffer 放进消息和 transfer list

关键不是只写第二个参数,而是让 transfer list 中的 buffer 同时出现在第一参数的消息对象里。transfer list 只声明哪些资源要转移,并不会替你把资源塞进消息。

// main.js:创建一块只交给 Worker 消费的二进制资源
const worker = new Worker(new URL("./parser-worker.js", import.meta.url), {
  type: "module",
});

const buffer = new ArrayBuffer(8 * 1024 * 1024);
const input = new Uint8Array(buffer);
input[0] = 42; // 用一个字节表示待处理数据,实际项目可来自文件或网络响应

// 发送端明确放弃 buffer 的所有权,避免 Worker 侧再复制整块内存
worker.postMessage({ kind: "parse", buffer }, [buffer]);

// 转移后只能读取长度确认 detached 状态,不能再访问 input 的内容
console.log(buffer.byteLength); // 0

worker.addEventListener("message", (event) => {
  // 这里只接收 Worker 返回的新所有权,不复用已经 detached 的旧引用
  const result = event.data;
  console.log(result.byteLength);
});

发送端的 buffer.byteLength 变为 0 是预期结果,不是传输失败。原来的 Uint8Array 只是一个视图,真正持有内存的是它的 .buffer;因此转移列表应写 [input.buffer] 或 [buffer],而不是把 Uint8Array 自身放进去。

Worker 侧只接收已转移的资源

Worker 收到消息后,可以把传入的 ArrayBuffer 包装成合适的 TypedArray,再进行解析。这里的重点是所有权已经交给 Worker,处理函数不要假设主线程还能够同时修改这块内存。

// parser-worker.js:在 Worker 侧建立视图并消费已接管的 buffer
self.addEventListener("message", (event) => {
  const { kind, buffer } = event.data;

  if (kind !== "parse" || !(buffer instanceof ArrayBuffer)) {
    // 对消息形状做最小保护,避免把普通对象误当成二进制输入
    self.postMessage({ kind: "error", message: "缺少可转移的 ArrayBuffer" });
    return;
  }

  // TypedArray 只是访问视图,底层内存仍由已经转移的 ArrayBuffer 持有
  const bytes = new Uint8Array(buffer);
  const checksum = bytes.reduce((sum, value) => (sum + value) % 65536, 0);

  // 处理结束后把同一块资源的所有权转回主线程,避免再次复制结果
  self.postMessage({ kind: "parsed", buffer, checksum }, [buffer]);
});

这里的返回消息把 buffer 放进了 payload,同时把它放进了返回 transfer list。主线程拿到的是新的所有者;Worker 返回后,Worker 侧的旧引用也不应再被读取。

把返回路径设计成明确的所有权边界

如果处理结果还要回到主线程,就把“谁拥有 buffer”当成接口契约,而不是隐藏在工具函数里。一个简单的约定是:进入 Worker 前主线程放弃所有权,Worker 处理期间独占,返回后主线程重新获得所有权。

Uint8Array、底层 ArrayBuffer、Worker 处理器和返回 transfer list 的静态结构说明图
图2:这是 TypedArray 与底层 ArrayBuffer 以及返回 transfer list 的静态结构图,用来标出可转移资源和结果交接边界,不是运行结果截图。
// main.js:接收 Worker 返回的所有权,并在本线程建立新的视图
worker.addEventListener("message", (event) => {
  const { kind, buffer, checksum } = event.data;

  if (kind === "parsed" && buffer instanceof ArrayBuffer) {
    // 返回后的 buffer 已属于主线程,可以重新创建读取视图
    const output = new Uint8Array(buffer);
    console.log("结果长度:", output.byteLength, "校验值:", checksum);
    return;
  }

  if (kind === "error") {
    // 错误消息不携带可转移资源,调用方可以按业务策略重试或提示用户
    console.error(event.data.message);
  }
});

四个容易踩到的边界

1. 只写 transfer list,接收侧拿不到 buffer

worker.postMessage({ kind: "parse" }, [buffer]) 会尝试转移资源,但消息里没有携带它。transfer list 不是第二份 payload;它只说明消息中已经可达的可转移资源应该移动到接收上下文。因此应写成 { buffer } 配合 [buffer]。

2. 把 Uint8Array 自身当作 transferable

Uint8Array 可以被 structured clone,但它本身不在可转移对象列表中。要转移它背后的内存,应传 view.buffer,并在接收侧根据需要重新建立视图。这样也更清楚地表达了:转移的是资源,不是某个视图对象。

3. 转移后继续读写旧引用

发送端的 ArrayBuffer 已经 detached,依赖它的视图也不能再当作有效输入。不要把转移动作藏在一个还会继续使用原变量的通用函数里;可以让函数返回“已交接”的状态,或者在交接后把旧变量从当前作用域中移除。

4. 同一资源重复转移

一个已经 detached 的 buffer 不能再次作为有效输入转移。批处理场景要为每个任务明确资源归属:任务入队后由 Worker 消费,任务取消时终止 Worker 或丢弃当前消息,不要让主线程和 Worker 同时保留“可写”的假设。

落地前的选择清单

  1. 数据是否足够大,复制成本是否真的值得用所有权管理来换取?
  2. 发送后主线程是否完全不再依赖原 ArrayBuffer?
  3. transfer list 中的对象是否真的出现在消息 payload 中?
  4. 如果输入是 TypedArray,是否传的是它的 .buffer?
  5. Worker 返回结果时,谁重新成为 buffer 的所有者,旧引用在哪里失效?
  6. 错误消息是否与二进制消息区分,避免错误分支读取不存在的 buffer?

一句话总结:postMessage(payload, [payload.buffer]) 不是“加速版复制”,而是一次资源所有权交接。只有当消费方可以独占这块内存时,转移才是合适的架构选择;一旦接受了发送端 detached 的生命周期,Web Worker 处理大块二进制数据就能少掉一份不必要的内存副本。

相关问题

为什么转移后 byteLength 是 0? 因为发送端已经不再拥有这块内存,ArrayBuffer 进入 detached 状态;这是转移成功后的可观察结果。

可以直接转移 Uint8Array 吗? 不可以把它作为 transferable 本体;应把它的底层 ArrayBuffer 放入消息与 transfer list。

所有 Worker 消息都应该用 transfer list 吗? 不应该。小对象、需要两侧独立副本的消息,普通 structured clone 更简单;只有资源独占和复制成本明确存在时才采用转移。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
ReadableStream 取消后怎样释放请求ReadableStream 取消后怎样释放请求
上一篇
ReadableStream 取消后怎样释放请求
IndexedDB 数据库升级怎样处理版本变更
下一篇
IndexedDB 数据库升级怎样处理版本变更
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    478次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    535次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    554次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    506次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    327次使用