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);
}
- 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();
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);
})();
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');
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`);
线程池实现
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");
const pool = new WorkerPool(2);
let finished = 0;
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");
class WorkerPoolTaskInfo extends AsyncResource {
constructor(callback) {
super("WorkerPoolTaskInfo");
this.callback = callback;
}
done(err, result) {
this.runInAsyncScope(this.callback, null, err, result);
this.emitDestroy();
}
}
class WorkerPool extends EventEmitter {
constructor(numThreads) {
super();
this.numThreads = numThreads;
this.workers = [];
this.freeWorkers = [];
this.tasks = [];
for (let i = 0; i < numThreads; i++) {
this.addNewWorker();
}
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[kTaskInfo] = null;
this.freeWorkers.push(worker);
this.emit(kWorkerFreedEvent);
});
worker.on("error", (err) => {
if (worker[kTaskInfo]) worker[kTaskInfo].done(err, null);
else this.emit("error", err);
this.workers.splice(this.workers.indexOf(worker), 1);
this.addNewWorker();
});
this.workers.push(worker);
this.freeWorkers.push(worker);
this.emit(kWorkerFreedEvent);
}
runTask(task, callback) {
if (this.freeWorkers.length === 0) {
this.tasks.push({ task, callback });
return;
}
const worker = this.freeWorkers.pop();
worker[kTaskInfo] = new WorkerPoolTaskInfo(callback);
worker.postMessage(task);
}
close() {
for (const worker of this.workers) worker.terminate();
}
}
module.exports = WorkerPool;