You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现域名级全局共享的React WorkerPool单例?

域名级全局共享WorkerPool解决方案

问题背景

当前实现的WorkerPool仅在单个标签页内是单例,同域名下的多个标签页会各自创建独立的WorkerPool实例,导致Web Worker线程重复创建,浪费系统资源。需要实现一个域名级全局共享的WorkerPool,让所有同域名标签页共用同一组Worker线程。

核心解决方案:使用SharedWorker

SharedWorker是浏览器提供的跨标签页共享的Worker类型,同域名下的所有页面都会连接到同一个SharedWorker实例,天然满足域名级全局共享的需求。我们需要将WorkerPool的核心逻辑迁移到SharedWorker中,前端页面通过消息通信与SharedWorker交互。

步骤1:实现SharedWorker端的全局WorkerPool

创建shared-worker-pool.js作为SharedWorker的脚本文件,在其中实现WorkerPool的管理逻辑:

import WorkerThread from './WorkerThread.js';

class GlobalWorkerPool {
  constructor() {
    this.taskQueue = [];
    this.workerQueue = [];
    this.poolSize = self.navigator.hardwareConcurrency || 8;
    this.connectedPorts = new Set(); // 管理所有连接的页面端口
    this.init();
  }

  init() {
    for (let i = 0; i < this.poolSize; i++) {
      this.workerQueue.push(new WorkerThread(this));
    }
  }

  addTask(taskData, port) {
    // 绑定任务对应的页面端口,用于回调
    taskData.port = port;
    if (this.workerQueue.length > 0) {
      const workerThread = this.workerQueue.shift();
      workerThread.run(taskData);
    } else {
      this.taskQueue.push(taskData);
    }
  }

  freeWorkerThread(workerThread) {
    if (this.taskQueue.length > 0) {
      const taskData = this.taskQueue.shift();
      workerThread.run(taskData);
    } else {
      this.workerQueue.unshift(workerThread);
    }
  }

  // 发送结果到对应的页面
  sendResult(taskId, result, port) {
    port.postMessage({ type: 'result', taskId, result });
  }

  sendProgress(taskId, progress, port) {
    port.postMessage({ type: 'progress', taskId, progress });
  }

  sendError(taskId, error, port) {
    port.postMessage({ type: 'error', taskId, error });
  }
}

// 初始化全局唯一的WorkerPool实例
const globalPool = new GlobalWorkerPool();

// 处理页面连接
self.onconnect = (e) => {
  const port = e.ports[0];
  globalPool.connectedPorts.add(port);

  port.onmessage = (msg) => {
    switch(msg.data.type) {
      case 'addTask':
        globalPool.addTask(msg.data.task, port);
        break;
      case 'disconnect':
        globalPool.connectedPorts.delete(port);
        break;
    }
  };

  port.start();
};

步骤2:改造前端页面的任务提交逻辑

页面不再直接创建本地WorkerPool,而是连接到SharedWorker,通过消息发送上传任务:

// 页面端代码
class SharedWorkerPoolClient {
  constructor() {
    this.sharedWorker = new SharedWorker('/shared-worker-pool.js');
    this.taskCallbacks = new Map(); // 存储每个任务的回调函数
    this.initMessageListener();
  }

  initMessageListener() {
    this.sharedWorker.port.onmessage = (msg) => {
      const { taskId, type, result, progress, error } = msg.data;
      const callbacks = this.taskCallbacks.get(taskId);
      if (!callbacks) return;

      switch(type) {
        case 'progress':
          callbacks.onProgress(progress);
          break;
        case 'result':
          callbacks.onResult(result);
          this.taskCallbacks.delete(taskId);
          break;
        case 'error':
          callbacks.onError(error);
          this.taskCallbacks.delete(taskId);
          break;
      }
    };
  }

  addWorkerTask(onProgress, onError, taskType, taskParams) {
    const taskId = crypto.randomUUID(); // 生成唯一任务ID
    this.taskCallbacks.set(taskId, { onProgress, onError });
    
    this.sharedWorker.port.postMessage({
      type: 'addTask',
      task: {
        taskId,
        taskType,
        params: taskParams
      }
    });

    return taskId;
  }

  destroy() {
    this.sharedWorker.port.postMessage({ type: 'disconnect' });
    this.sharedWorker.port.close();
  }
}

// 页面内单例使用
const poolClient = new SharedWorkerPoolClient();

// 提交上传任务的代码改造
const taskId = poolClient.addWorkerTask(
  handleFileUploadProgress,
  handleFileUploadError,
  WorkerType.Upload,
  {
    nameBase: nameBase,
    file: file, // 注意:SharedWorker传输大文件建议用Transferable Objects
    name: file.name,
    fileTags: tagMap[file.name] ?? {},
    metadataId,
    timeoutLength: UPLOAD_TIMEOUT_LENGTH,
    url: process.env.NODE_ENV === 'development' ? 'http://localhost:9100' : `${window.location.origin}`,
  }
);

步骤3:改造WorkerThread适配SharedWorker

原来的WorkerThread需要调整为在SharedWorker环境中运行,并且通过SharedWorker的端口回调结果到对应页面:

// WorkerThread.js 改造
export default class WorkerThread {
  constructor(pool) {
    this.pool = pool;
    this.currentTask = null;
    // 创建内部专用Worker处理上传(避免阻塞SharedWorker)
    this.worker = new Worker('./upload-worker.js');
    this.initWorkerListener();
  }

  initWorkerListener() {
    this.worker.onmessage = (msg) => {
      const { taskId, type, data } = msg.data;
      const { port } = this.currentTask;
      
      switch(type) {
        case 'progress':
          this.pool.sendProgress(taskId, data, port);
          break;
        case 'complete':
          this.pool.sendResult(taskId, data, port);
          this.pool.freeWorkerThread(this);
          break;
        case 'error':
          this.pool.sendError(taskId, data, port);
          this.pool.freeWorkerThread(this);
          break;
      }
    };
  }

  run(taskData) {
    this.currentTask = taskData;
    this.worker.postMessage({
      taskId: taskData.taskId,
      taskType: taskData.taskType,
      params: taskData.params
    }, [taskData.params.file]); // 使用Transferable Objects传递文件,避免复制
  }
}

补充说明

  • 为什么本地单例无效?:每个浏览器标签页都是独立的JavaScript执行环境,单例模式仅在当前环境内有效,无法跨标签页共享状态或资源。
  • SharedWorker的兼容性:主流浏览器(Chrome、Firefox、Edge)均支持SharedWorker,需要注意Safari的部分版本可能存在限制。
  • 资源清理:当所有页面断开连接后,SharedWorker会自动终止,无需手动销毁Worker线程。

内容的提问来源于stack exchange,提问作者Jeremy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 18:46:18