Node Worker Threads Pool for CPU Tasks
Offload CPU-bound work to a pool of worker threads with Piscina-style sizing, transferable buffers, and graceful pool shutdown.
Code
import { Worker } from 'node:worker_threads';
import { mkdtemp, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
const WORKER_SOURCE = `
const { parentPort, workerData } = require('node:worker_threads');
function fib(n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); }
parentPort.postMessage({ input: workerData.n, result: fib(workerData.n) });
`;
class WorkerPool {
constructor(size) {
this.size = size;
this.workers = [];
this.queue = [];
this.busy = new Set();
}
async init() {
const dir = await mkdtemp(join(tmpdir(), 'pool-'));
this.file = join(dir, 'worker.js');
await writeFile(this.file, WORKER_SOURCE, 'utf8');
for (let i = 0; i < this.size; i++) this.workers.push(new Worker(this.file));
}
run(n) {
return new Promise((resolve, reject) => {
this.queue.push({ n, resolve, reject });
this.dispatch();
});
}
dispatch() {
while (this.queue.length > 0 && this.busy.size < this.size) {
const job = this.queue.shift();
const worker = this.workers.find((w) => !this.busy.has(w));
this.busy.add(worker);
worker.once('message', (msg) => {
this.busy.delete(worker);
job.resolve(msg.result);
this.dispatch();
});
worker.postMessage({ n: job.n });
}
}
async destroy() {
await Promise.all(this.workers.map((w) => w.terminate()));
}
}
const pool = new WorkerPool(4);
await pool.init();
console.log(await pool.run(35));
await pool.destroy();Report a problem with this exampleOpens a pre-filled issue on GitHub.