JavaScript:while循环内如何与Web Worker通信处理任务?
手动调用栈转多Web Worker并行执行的问题
你原本用手动调用栈处理递归计算,代码如下:
var stack = initialListOfTasks(); while (stack.length > 0) { var todo = stack.shift(); performTask(stack, todo); }
performTask()函数会向栈中添加新任务,运行正常。由于各任务可独立执行,你希望将栈中任务分配给多个Web Worker以提升运行速度,但在遍历任务的while循环中无法与Worker通信。
你尝试的代码如下:
var stack = initialListOfTasks(); var n_workers = navigator.hardwareConcurrency; var n_workers_in_use = 0; while (stack.length > 0 || n_workers_in_use > 0) { if (stack.length > 0 && n_workers_in_use < n_workers) { var todo = stack.shift(); var worker = new Worker("worker.js"); worker.postMessage(todo); n_workers_in_use++; worker.onmessage = function(obj) { var new_tasks = obj.data; stack = stack.concat(new_tasks); n_workers_in_use--; worker.terminate(); }; } }
这段代码会造成无限循环,因为n_workers_in_use被递增后无法递减,onmessage函数从未触发。你也尝试在循环外创建多个Worker,在循环内发消息,但同样无效,只有手动退出循环后onmessage才会触发。
你想知道:能否让onmessage在while循环内触发并修改stack和n_workers_in_use的值?或者有没有无需while循环的实现方式?
问题根源
JavaScript是单线程模型,主线程被while循环完全占用时,事件队列里的所有异步回调(包括Worker的onmessage)都无法得到执行机会。这就导致n_workers_in_use永远不会递减,循环条件始终为真,陷入无限循环。
解决方案:事件驱动的任务调度
直接抛弃阻塞式的while循环,改用事件驱动的方式调度任务,让Worker完成后的回调主动触发下一轮任务分配。
方案1:动态创建Worker(适合任务量较小的场景)
var stack = initialListOfTasks(); // 兼容不支持navigator.hardwareConcurrency的环境 var n_workers = navigator.hardwareConcurrency || 4; var n_workers_in_use = 0; // 任务调度函数 function scheduleTask() { // 尽可能填满空闲的Worker while (stack.length > 0 && n_workers_in_use < n_workers) { const todo = stack.shift(); const worker = new Worker("worker.js"); worker.postMessage(todo); n_workers_in_use++; // 处理Worker返回结果 worker.onmessage = function(obj) { const new_tasks = obj.data; stack = stack.concat(new_tasks); n_workers_in_use--; worker.terminate(); // 任务完成后,继续调度下一批 scheduleTask(); }; // 处理Worker错误 worker.onerror = function(err) { console.error("Worker执行出错:", err); n_workers_in_use--; worker.terminate(); scheduleTask(); }; } // 所有任务处理完毕且无Worker在运行 if (stack.length === 0 && n_workers_in_use === 0) { console.log("所有任务执行完成"); } } // 启动调度 scheduleTask();
方案2:Worker池复用(适合任务量大的场景)
频繁创建/销毁Worker会带来性能开销,用Worker池复用已创建的Worker更高效:
var stack = initialListOfTasks(); var n_workers = navigator.hardwareConcurrency || 4; var workerPool = []; var isProcessing = false; // 初始化Worker池 function initWorkerPool() { for (let i = 0; i < n_workers; i++) { const worker = new Worker("worker.js"); worker.onmessage = handleWorkerMessage; worker.onerror = handleWorkerError; // 标记Worker是否空闲 worker.isIdle = true; workerPool.push(worker); } } // 统一处理Worker返回结果 function handleWorkerMessage(obj) { const worker = this; const new_tasks = obj.data; stack = stack.concat(new_tasks); worker.isIdle = true; // 继续调度任务 scheduleTasks(); } // 统一处理Worker错误 function handleWorkerError(err) { const worker = this; console.error("Worker执行出错:", err); worker.isIdle = true; scheduleTasks(); } // 任务调度函数 function scheduleTasks() { if (isProcessing) return; isProcessing = true; // 给所有空闲Worker分配任务 for (const worker of workerPool) { if (worker.isIdle && stack.length > 0) { const todo = stack.shift(); worker.isIdle = false; worker.postMessage(todo); } } isProcessing = false; // 检查是否所有任务都完成 const allWorkersIdle = workerPool.every(worker => worker.isIdle); if (stack.length === 0 && allWorkersIdle) { console.log("所有任务执行完成"); // 终止所有Worker workerPool.forEach(worker => worker.terminate()); } } // 启动流程 initWorkerPool(); scheduleTasks();
内容的提问来源于stack exchange,提问作者Jan Mulder
相关产品推荐
相关产品推荐

