← 中文资源

简体中文版本

基于 worker_threads 的线程池

在固定数量的 Node 工作线程上运行 CPU 任务,支持任务队列、超时、崩溃恢复与干净关闭。

阅读英文原文

功能说明

WorkerPool 创建固定数量的 worker_threads 工作线程,并通过队列把任务分发给空闲线程。每个任务带唯一 id,线程返回 { id, ok, result } 或 { id, ok: false, error },主线程据此完成对应的 Promise。它适合图像处理、压缩、加解密、解析大文件等会阻塞事件循环的 CPU 密集型工作。

池子限制同时运行的任务数,超出的任务排队等待。任务有超时保护:超时后拒绝 Promise、终止该线程并创建替代线程,避免一个卡死任务永久占用槽位。线程崩溃或异常退出时,挂在该线程上的任务会失败,池子自动补充新线程,队列中的其他任务继续执行。close() 会拒绝排队和进行中的任务,并等待所有线程终止。

关键代码

分发任务时同时登记 pending 记录和超时计时器;收到匹配 id 的消息后清除计时器并完成 Promise:

const timer = setTimeout(() => {
  const entry = this.pending.get(id);
  if (entry === undefined) return;
  this.pending.delete(id);
  entry.reject(
    new Error(`task ${id} timed out after ${this.taskTimeoutMs}ms`),
  );
  this.replaceWorker(worker);
}, this.taskTimeoutMs);

this.pending.set(id, { worker, resolve, reject, timer });
worker.postMessage({ id, payload });

线程返回失败消息时使用 Error 包装错误文本,保证调用方拿到可捕获的异常对象。

使用注意

消息通过结构化克隆传递,payload 和结果必须是可克隆的值,函数、Symbol 和部分宿主对象会触发 DataCloneError;示例捕获该异常并拒绝任务。工作线程的代码在示例中以字符串加 eval: true 启动,生产环境更适合使用独立文件或 workerUrl,便于调试和打包。

线程池大小应结合 CPU 核数和任务类型调整,CPU 密集任务通常不超过核数。超时会终止线程,已产生的副作用无法回滚,任务应设计为幂等。close() 会拒绝所有未完成任务,不会等待它们自然结束。线程崩溃恢复依赖 error 和 exit 事件,若线程陷入死循环只能靠超时终止。当前实现没有优先级和任务重试。

验证方式

示例创建 2 个线程,同时提交三组求和任务,结果应为 [6, 15, 24],说明队列按空闲槽位调度。随后提交包含字符串的非法 payload,工作线程捕获错误并返回 values must be finite numbers,主线程的 catch 得到同一消息。调用 await pool.close() 后输出 closed,进程应能正常退出,没有残留线程。