node 多线程 worker_threads

node 多线程产生原因

  • node 本身通过事件循环能够很好地处理 I/O 密集型场景(文件读写等操作),不会阻塞主线程,而是通过回调形式通知主线程结果
  • 但是当面对 cpu 密集型场景时,比如主线程有大量的数学计算等工作依旧会阻塞主线程,所以产生了多线程的需求

多线程特点

  • 与多进程不同,线程之间可以共享内存,通过共享 SharedArrayBuffer 来实现
  • 每个线程都在与其他线程隔离的情况下执行。但是这些线程可以通过消息通道来回传递消息。

worker:创建线程

文档

  • workerData:数据根据 HTML 结构化克隆算法被克隆
const { Worker, isMainThread, workerData } = require('node:worker_threads');

if (isMainThread) {
  const worker = new Worker(__filename, { workerData: 'Hello, world!' }); // 主线程传递
} else {
  console.log(workerData);  // 'Hello, world!' 子线程接收
}
  • worker.ref()
    • 用于确保 worker 仍保持活动状态,以便在主线程退出时不会因为 worker 实例不再活动而自动退出。如果从未调用过 worker.unref(),则无需调用 worker.ref(),默认情况下 worker 是引用的(ref)状态
  • woker.unref()
    • 用于将 worker 设置为 unreferenced 状态。在非引用状态下,即使 worker 仍在运行,主线程也可能退出。
    • 主线程不会等待非引用的 worker 完成并在结束时自动关闭。这对于在主线程中创建非关键性任务(如清理程序)的 worker 线程非常有用,这些任务可以在主线程结束后立即停止执行。
    • 即在主线程退出时,worker 线程不会阻止程序退出,即使 worker 仍在进行中。在这种情况下,工作线程会在主线程退出后被强制终止,不再等待任务完成。
const { Worker, isMainThread, parentPort } = require('worker_threads');

if (isMainThread) {
  const worker = new Worker(__filename);

  worker.unref(); // 将 worker 设置为不再引用状态
  console.log('Main thread exiting...');
} else {
  console.log('Worker running...');
  // 模拟长时间运行的任务
  setTimeout(() => {
    console.log("Worker task finished.");
  }, 10000);
}

worker_threads 线程空闲不会被回收

  • 一旦创建了一个 Worker,它将持续运行,直到显式地终止,主线程退出,或者出现未捕获的异常
  • 若要停止工作线程并释放相关资源
    • worker.terminate():在主线程调用此方法以终止工作线程。这会立即停止其执行并释放相关资源。
    • parentPort.close():在 worker 线程内部,你可以调用 parentPort.close() 结束消息通道。这将中断主线程与工作线程之间的通信,并允许 worker 线程在完成剩余任务后自动退出

worker_threads 实现内存共享

  • 通过 SharedArrayBuffer 创建共享内存缓冲区,通过 Atomics 实现对 SharedArrayBuffer 的原子操作(文档
// 主线程
const { Worker } = require('worker_threads');

function runWorker(filename, sharedBuffer) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(filename, {
      workerData: { sharedBuffer }
    });

    worker.on('message', resolve);
    worker.on('error', reject);
    worker.on('exit', code => {
      if (code !== 0) {
        reject(new Error(`Worker stopped with exit code ${code}`));
      }
    });
  });
}

(async () => {
  const sharedBuffer = new SharedArrayBuffer(4); 
  const workerAResult = await runWorker('./worker_A.js', sharedBuffer);
  const workerBResult = await runWorker('./worker_B.js', sharedBuffer);

  console.log('worker A:', workerAResult);
  console.log('worker B:', workerBResult);
})();
// 线程A
const { parentPort, workerData } = require('worker_threads');

const uint32 = new Uint32Array(workerData.sharedBuffer);

Atomics.add(uint32, 0, 1);
parentPort.postMessage('Worker A added 1 to shared buffer');

// 线程B
const { parentPort, workerData } = require('worker_threads');

const uint32 = new Uint32Array(workerData.sharedBuffer);

const currentValue = Atomics.load(uint32, 0);
parentPort.postMessage(`Worker B read value ${currentValue} from shared buffer`);

// 主线程打印
// worker A: Worker A added 1 to shared buffer
// worker B: Worker B read value 1 from shared buffer

线程池实现

  • worker

    • 用于计算任务
// task_processor.js
const { parentPort } = require("node:worker_threads");
parentPort.on("message", (task) => {
  parentPort.postMessage(task.a + task.b);
});

  • 主线程通过线程池分发任务

const WorkerPool = require("./worker_pool.js");
const os = require("node:os");

// 线程池内有 2 个线程
const pool = new WorkerPool(2);

let finished = 0;
// 分发10个任务给线程池
for (let i = 0; i < 10; i++) {
  pool.runTask({ a: 42, b: 100 }, (err, result) => {
    // 线程池每次执行完一个任务打印结果
    console.log(i, err, result);
    if (++finished === 10) pool.close(); // 执行完所有任务关闭线程池释放资源
  });
}

  • 线程池实现

const { AsyncResource } = require("node:async_hooks");
const { EventEmitter } = require("node:events");
const path = require("node:path");
const { Worker } = require("node:worker_threads");

const kTaskInfo = Symbol("kTaskInfo");
const kWorkerFreedEvent = Symbol("kWorkerFreedEvent");

// 对主线程传入的任务的封装,继承 AsyncResource 使得任务可以被 async_hook 捕捉
class WorkerPoolTaskInfo extends AsyncResource {
  constructor(callback) {
    super("WorkerPoolTaskInfo");
    this.callback = callback;
  }

  done(err, result) {
    this.runInAsyncScope(this.callback, null, err, result); // result 为通过 worker 计算出的结果
    this.emitDestroy(); // `TaskInfo`s are used only once.
  }
}

// 继承 EventEmitter ,使得具有 发布-订阅 功能
class WorkerPool extends EventEmitter {
  constructor(numThreads) {
    super();
    this.numThreads = numThreads; // 开启的线程数量
    this.workers = []; // 保存创建的worker
    this.freeWorkers = []; // 保存空闲的worker
    this.tasks = []; // 保存要执行的任务

    for (let i = 0; i < numThreads; i++) {
      this.addNewWorker(); // 根据线程数量添加 woker
    }

    // Any time the kWorkerFreedEvent is emitted, dispatch
    // the next task pending in the queue, if any.
    // 触发 worker 执行任务
    this.on(kWorkerFreedEvent, () => {
      if (this.tasks.length > 0) {
        const { task, callback } = this.tasks.shift();
        this.runTask(task, callback);
      }
    });
  }

  addNewWorker() {
    const worker = new Worker(path.resolve(__dirname, "task_processor.js"));
    worker.on("message", (result) => {
      worker[kTaskInfo].done(null, result); // worker 计算出的结果返回给到 WorkerPoolTaskInfo 中去执行回调
      worker[kTaskInfo] = null; // 执行完任务清除当前 worker 绑定的内容
      this.freeWorkers.push(worker); // 标识为空闲 worker
      this.emit(kWorkerFreedEvent); // 通知空闲 worker 执行下一个任务
    });
    worker.on("error", (err) => {
      // 错误处理
      if (worker[kTaskInfo]) worker[kTaskInfo].done(err, null);
      else this.emit("error", err);
      // 删除该worker,重新创建一个worker
      this.workers.splice(this.workers.indexOf(worker), 1);
      this.addNewWorker();
    });
    this.workers.push(worker);
    this.freeWorkers.push(worker);
    this.emit(kWorkerFreedEvent); // 为了 worker 出错时,让新增加的 worker 执行任务
  }

  runTask(task, callback) {
  	// 如果没有空闲 worker,缓存任务到任务队列中
    if (this.freeWorkers.length === 0) {
      // No free threads, wait until a worker thread becomes free.
      this.tasks.push({ task, callback });
      return;
    }
    // 获取空闲 worker
    const worker = this.freeWorkers.pop();
    // 封装主线程传递的回调,通过 runInAsyncScope 使得回调执行可以被追踪
    worker[kTaskInfo] = new WorkerPoolTaskInfo(callback);
    // 通知 worker 执行任务
    worker.postMessage(task);
  }

  close() {
    // 
    for (const worker of this.workers) worker.terminate(); // 终止工作线程释放资源
  }
}

module.exports = WorkerPool;

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值