Web Worker多线程计算与SharedArrayBuffer共享内存实战

Web Worker多线程模型与浏览器并行计算

JavaScript单线程执行模型在计算密集型任务面前存在天然瓶颈。Web Worker为浏览器提供了真正的操作系统级线程,Worker线程运行在独立的全局上下文中,无法直接访问DOM,但可以执行任意JavaScript计算。主线程与Worker之间通过postMessage通信,数据默认采用结构化克隆算法复制传输。

Web Worker分为Dedicated Worker和Shared Worker两类。Dedicated Worker一对一绑定创建者,Shared Worker可被多个页面共享。计算密集型场景推荐Dedicated Worker,跨页面通信推荐Shared Worker。

Dedicated Worker基本通信与数据传输

主线程创建Worker并建立双向通信:

// main.js - 主线程
const worker = new Worker('compute-worker.js');

// 发送任务到Worker
worker.postMessage({
  type: 'START_COMPUTE',
  data: largeArray,
  config: { iterations: 1000, threshold: 0.001 }
});

// 接收Worker返回结果
worker.onmessage = (event) => {
  const { type, result, progress } = event.data;
  if (type === 'PROGRESS') {
    updateProgressBar(progress);
  } else if (type === 'COMPLETE') {
    renderResult(result);
  }
};

// compute-worker.js - Worker线程
self.onmessage = (event) => {
  const { type, data, config } = event.data;

  if (type === 'START_COMPUTE') {
    const result = heavyComputation(data, config);
    self.postMessage({ type: 'COMPLETE', result });
  }
};

function heavyComputation(data, config) {
  let result = [];
  for (let i = 0; i < config.iterations; i++) {
    const partial = processChunk(data, i, config.threshold);
    result.push(partial);

    if (i % (config.iterations / 10) === 0) {
      self.postMessage({ type: 'PROGRESS', progress: i / config.iterations });
    }
  }
  return result;
}

postMessage默认使用结构化克隆复制数据,对于大型ArrayBuffer会产生显著的拷贝开销。10MB数据传输可能消耗数十毫秒,在高频通信场景下会成为瓶颈。

Transferable Objects零拷贝传输机制

Transferable Objects机制允许将ArrayBuffer的所有权从主线程转移给Worker,实现零拷贝传输。转移后原线程对该缓冲区的引用将失效:

// 主线程 - 零拷贝传输
const buffer = new Float64Array(1000000);  // 约8MB数据
// 填充数据...

// 通过transferList转移buffer所有权
worker.postMessage({ type: 'PROCESS', buffer: buffer.buffer }, [buffer.buffer]);

// 转移后,buffer在主线程变为0长度
console.log(buffer.length);  // 0 - 所有权已转移

// Worker端接收
self.onmessage = (event) => {
  const { buffer } = event.data;
  const floatArray = new Float64Array(buffer);
  // 直接操作,无需拷贝
  const result = computeInPlace(floatArray);

  // 将结果转移回主线程
  self.postMessage({ type: 'RESULT', buffer: result.buffer }, [result.buffer]);
};

Transferable Objects支持的类型包括:ArrayBuffer、MessagePort、ImageBitmap、OffscreenCanvas。传输后原线程引用被剥离,避免双写导致的内存占用倍增和竞态条件。

SharedArrayBuffer多线程共享内存编程

SharedArrayBuffer允许多个Worker线程共享同一块内存区域,无需数据拷贝即可实现线程间数据交换。SharedArrayBuffer需要严格的安全上下文(Cross-Origin-Isolation):

// 服务端必须配置COOP/COEP响应头
// Cross-Origin-Opener-Policy: same-origin
// Cross-Origin-Embedder-Policy: require-corp

// 主线程创建共享缓冲区
const sharedBuffer = new SharedArrayBuffer(4 * 1024 * 1024);  // 4MB共享内存
const sharedArray = new Float64Array(sharedBuffer);

// 初始化数据
for (let i = 0; i < sharedArray.length; i++) {
  sharedArray[i] = Math.random();
}

// 创建多个Worker共享同一内存
const workerCount = navigator.hardwareConcurrency || 4;
const workers = [];
const chunkSize = sharedArray.length / workerCount;

for (let i = 0; i < workerCount; i++) {
  const worker = new Worker('shared-worker.js');
  worker.postMessage({
    sharedBuffer,
    offset: i * chunkSize,
    length: chunkSize,
    workerId: i
  });
  workers.push(worker);
}

Worker端使用Atomics操作保证共享内存的原子访问:

// shared-worker.js
self.onmessage = (event) => {
  const { sharedBuffer, offset, length, workerId } = event.data;
  const sharedArray = new Float64Array(sharedBuffer);

  // 并行计算各自负责的分片
  for (let i = offset; i < offset + length; i++) {
    sharedArray[i] = Math.sqrt(sharedArray[i]) * 2.5;
  }

  // 使用Atomics同步完成信号
  const syncBuffer = new Int32Array(sharedBuffer);
  const finishedIndex = syncArray.length - 1;
  const prev = Atomics.add(syncArray, finishedIndex, 1);

  if (prev + 1 === workerCount) {
    self.postMessage({ type: 'ALL_COMPLETE' });
  }
};

Atomics原子操作与同步原语

SharedArrayBuffer的并发访问必须通过Atomics对象进行同步。Atomics提供了load、store、add、sub、and、or、xor、compareExchange、exchange等原子操作,以及wait/notify实现类似条件变量的同步机制:

// 使用Atomics实现简单的自旋锁
class SpinLock {
  constructor(sharedBuffer, byteOffset) {
    this.lock = new Int32Array(sharedBuffer, byteOffset, 1);
  }

  acquire() {
    while (Atomics.compareExchange(this.lock, 0, 0, 1) !== 0) {
      // 自旋等待,可加入短暂yield降低CPU占用
    }
  }

  release() {
    Atomics.store(this.lock, 0, 0);
  }
}

// 使用Atomics.wait/notify实现生产者-消费者
Atomics.store(dataArray, writeIndex, value);
Atomics.notify(dataArray, writeIndex);

Atomics.wait(dataArray, readIndex, expectedValue, timeout);

Web Worker与SharedArrayBuffer的组合在图像处理、大规模数值计算、音视频编解码等场景中可带来2-8倍的性能提升,具体取决于CPU核心数和计算密集程度。需要特别注意浏览器兼容性,SharedArrayBuffer目前仅在安全上下文下可用,部分企业网络代理可能会剥离COOP/COEP响应头导致功能降级。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/webworker-duo-xian-cheng-ji-suan-yu-sharedarraybuffer-gong/

(0)
小编小编
上一篇 6小时前
下一篇 6小时前

相关推荐