如何实现域名级全局共享的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
相关产品推荐
相关产品推荐

