Worker Pools in JavaScript
In this example, we’ll look at how to implement a worker pool using JavaScript’s asynchronous features.
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
// Here's the worker, of which we'll run several concurrent instances.
// These workers will receive work on the `workerData` and send the corresponding
// results back to the main thread. We'll use a setTimeout to simulate an expensive task.
if (!isMainThread) {
const worker = async (id, job) => {
console.log(`worker ${id} started job ${job}`);
await new Promise(resolve => setTimeout(resolve, 1000));
console.log(`worker ${id} finished job ${job}`);
parentPort.postMessage(job * 2);
};
worker(workerData.id, workerData.job);
}
// Main thread code
if (isMainThread) {
const numJobs = 5;
const numWorkers = 3;
let completedJobs = 0;
// This function creates a new worker
const createWorker = (id, job) => {
return new Promise((resolve) => {
const worker = new Worker(__filename, {
workerData: { id, job }
});
worker.on('message', (result) => {
console.log(`Job ${job} returned ${result}`);
completedJobs++;
if (completedJobs === numJobs) {
console.log('All jobs completed');
}
resolve(result);
});
});
};
// This starts up our worker pool
const runJobs = async () => {
const workers = [];
for (let i = 1; i <= numJobs; i++) {
const workerId = (i - 1) % numWorkers + 1;
workers.push(createWorker(workerId, i));
// If we've queued up enough jobs to fill the worker pool,
// wait for one to complete before queueing more
if (workers.length === numWorkers) {
await Promise.race(workers);
workers.length = 0;
}
}
// Wait for any remaining jobs
await Promise.all(workers);
};
console.time('Jobs completed in');
runJobs().then(() => console.timeEnd('Jobs completed in'));
}
In this JavaScript implementation, we use the worker_threads
module to create a worker pool. The main differences from the original example are:
- Instead of channels, we use the
Worker
class and message passing. - We simulate the job queue by creating workers on demand, up to the maximum number of workers.
- We use
Promise
s to handle the asynchronous nature of the workers.
To run the program, save it as worker-pools.js
and use node
:
$ node worker-pools.js
worker 1 started job 1
worker 2 started job 2
worker 3 started job 3
worker 1 finished job 1
Job 1 returned 2
worker 1 started job 4
worker 2 finished job 2
Job 2 returned 4
worker 2 started job 5
worker 3 finished job 3
Job 3 returned 6
worker 1 finished job 4
Job 4 returned 8
worker 2 finished job 5
Job 5 returned 10
All jobs completed
Jobs completed in: 2.023s
Our running program shows the 5 jobs being executed by various workers. The program only takes about 2 seconds despite doing about 5 seconds of total work because there are 3 workers operating concurrently.
This example demonstrates how to implement a worker pool pattern in JavaScript, which can be useful for distributing CPU-intensive tasks across multiple threads.