workerpool
workerpool offers an easy way to create a pool of workers for both dynamically offloading computations as well as managing a pool of dedicated workers. workerpool basically implements a thread pool pattern. There is a pool of workers to execute tasks. New tasks are put in a queue. A worker executes one task at a time, and once finished, picks a new task from the queue. Workers can be accessed via a natural, promise based proxy, as if they are available straight in the main application.
workerpool runs on Node.js and in the browser.
Why
JavaScript is based upon a single event loop which handles one event at a time. Jeremy Epstein explains this clearly:
In Node.js everything runs in parallel, except your code. What this means is that all I/O code that you write in Node.js is non-blocking, while (conversely) all non-I/O code that you write in Node.js is blocking.
This means that CPU heavy tasks will block other tasks from being executed. In case of a browser environment, the browser will not react to user events like a mouse click while executing a CPU intensive task (the browser “hangs”). In case of a node.js server, the server will not respond to any new request while executing a single, heavy request.
For front-end processes, this is not a desired situation. Therefore, CPU intensive tasks should be offloaded from the main event loop onto dedicated workers. In a browser environment, Web Workers can be used. In node.js, child processes and worker_threads are available. An application should be split in separate, decoupled parts, which can run independent of each other in a parallelized way. Effectively, this results in an architecture which achieves concurrency by means of isolated processes and message passing.
Install
Install via npm:
npm install workerpool
Load
To load workerpool in a node.js application (both main application as well as workers):
const workerpool = require('workerpool');
To load workerpool in the browser:
<script src="workerpool.js"></script>
To load workerpool in a web worker in the browser:
importScripts('workerpool.js');
Setting up the workerpool with React or webpack5 requires additional configuration steps, as outlined in the webpack5 section.
Use
Offload functions dynamically
In the following example there is a function add
, which is offloaded dynamically to a worker to be executed for a given set of arguments.
myApp.js
const workerpool = require('workerpool');
const pool = workerpool.pool();
function add(a, b) {
return a + b;
}
pool
.exec(add, [3, 4])
.then(function (result) {
console.log('result', result); // outputs 7
})
.catch(function (err) {
console.error(err);
})
.then(function () {
pool.terminate(); // terminate all workers when done
});
Note that both function and arguments must be static and stringifiable, as they need to be sent to the worker in a serialized form. In case of large functions or function arguments, the overhead of sending the data to the worker can be significant.
Dedicated workers
A dedicated worker can be created in a separate script, and then used via a worker pool.
myWorker.js
const workerpool = require('workerpool');
// a deliberately inefficient implementation of the fibonacci sequence
function fibonacci(n) {
if (n < 2) return n;
return fibonacci(n - 2) + fibonacci(n - 1);
}
// create a worker and register public functions
workerpool.worker({
fibonacci: fibonacci,
});
This worker can be used by a worker pool:
myApp.js
const workerpool = require('workerpool');
// create a worker pool using an external worker script
const pool = workerpool.pool(__dirname + '/myWorker.js');
// run registered functions on the worker via exec
pool
.exec('fibonacci', [10])
.then(function (result) {
console.log('Result: ' + result); // outputs 55
})
.catch(function (err) {
console.error(err);
})
.then(function () {
pool.terminate(); // terminate all workers when done
});
// or run registered functions on the worker via a proxy:
pool
.proxy()
.then(function (worker) {
return worker.fibonacci(10);
})
.then(function (result) {
console.log('Result: ' + result); // outputs 55
})
.catch(function (err) {
console.error(err);
})
.then(function () {
pool.terminate(); // terminate all workers when done
});
Worker can also initialize asynchronously:
myAsyncWorker.js
define(['workerpool/dist/workerpool'], function (workerpool) {
// a deliberately inefficient implementation of the fibonacci sequence
function fibonacci(n) {
if (n < 2) return n;
return fibonacci(n - 2) + fibonacci(n - 1);
}
// create a worker and register public functions
workerpool.worker({
fibonacci: fibonacci,
});
});
Examples
Examples are available in the examples directory:
https://github.com/josdejong/workerpool/tree/master/examples
API
The API of workerpool consists of two parts: a function workerpool.pool
to create a worker pool, and a function workerpool.worker
to create a worker.
Events
You can send data back from workers to the pool while the task is being executed using the workerEmit
function:
workerEmit(payload: any) : unknown
This function only works inside a worker and during a task.
Example:
// file myWorker.js
const workerpool = require('workerpool');
function eventExample(delay) {
workerpool.workerEmit({
status: 'in_progress',
});
workerpool.workerEmit({
status: 'complete',
});
return true;
}
// create a worker and register functions
workerpool.worker({
eventExample: eventExample,
});
To receive those events, you can use the on
option of the pool exec
method:
pool.exec('eventExample', [], {
on: function (payload) {
if (payload.status === 'in_progress') {
console.log('In progress...');
} else if (payload.status === 'complete') {
console.log('Done!');
}
},
});
Utilities
Following properties are available for convenience:
- platform: The Javascript platform. Either node or browser
- isMainThread: Whether the code is running in main thread or not (Workers)
- cpus: The number of CPUs/cores available
Related libraries
- https://github.com/andywer/threads.js
- https://github.com/piscinajs/piscina
- https://github.com/learnboost/cluster
- https://github.com/adambom/parallel.js
- https://github.com/padolsey/operative
- https://github.com/calvinmetcalf/catiline
- https://github.com/Unitech/pm2
- https://github.com/godaddy/node-cluster-service
- https://github.com/ramesaliyev/EasyWebWorker
- https://github.com/rvagg/node-worker-farm
Build
First clone the project from github:
git clone git://github.com/josdejong/workerpool.git
cd workerpool
Install the project dependencies:
npm install
Then, the project can be build by executing the build script via npm:
npm run build
This will build the library workerpool.js and workerpool.min.js from the source files and put them in the folder dist.
Test
To execute tests for the library, install the project dependencies once:
npm install
Then, the tests can be executed:
npm test
To test code coverage of the tests:
npm run coverage
To see the coverage results, open the generated report in your browser:
./coverage/index.html
Publish
- Describe changes in HISTORY.md
- Update version in package.json, run
npm install
to update it inpackage-lock.json
too. - Push to Github
- Deploy to npm via
npm publish
- Add a git tag with the version number like:
git tag v1.2.3 git push --tags