Workerpool

Offload tasks to a pool of workers on node.js and in the browser

README

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, Chrome, Firefox, Opera, Safari, and IE10+.

Features


- Easy to use
- Runs in the browser and on node.js
- Dynamically offload functions to a worker
- Access workers via a proxy
- Cancel running tasks
- Set a timeout on tasks
- Handles crashed workers
- Small: 7 kB minified and gzipped
- Supports transferable objects (only for web workers and worker_threads)

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):

  1. ```js
  2. const workerpool = require('workerpool');
  3. ```

To load workerpool in the browser:

  1. ```html
  2. <script src="workerpool.js"></script>
  3. ```

To load workerpool in a web worker in the browser:

  1. ```js
  2. importScripts('workerpool.js');
  3. ```

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

  1. ```js
  2. const workerpool = require('workerpool');
  3. const pool = workerpool.pool();

  4. function add(a, b) {
  5.   return a + b;
  6. }

  7. pool
  8.   .exec(add, [3, 4])
  9.   .then(function (result) {
  10.     console.log('result', result); // outputs 7
  11.   })
  12.   .catch(function (err) {
  13.     console.error(err);
  14.   })
  15.   .then(function () {
  16.     pool.terminate(); // terminate all workers when done
  17.   });
  18. ```

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

  1. ```js
  2. const workerpool = require('workerpool');

  3. // a deliberately inefficient implementation of the fibonacci sequence
  4. function fibonacci(n) {
  5.   if (n < 2) return n;
  6.   return fibonacci(n - 2) + fibonacci(n - 1);
  7. }

  8. // create a worker and register public functions
  9. workerpool.worker({
  10.   fibonacci: fibonacci,
  11. });
  12. ```

This worker can be used by a worker pool:

myApp.js

  1. ```js
  2. const workerpool = require('workerpool');

  3. // create a worker pool using an external worker script
  4. const pool = workerpool.pool(__dirname + '/myWorker.js');

  5. // run registered functions on the worker via exec
  6. pool
  7.   .exec('fibonacci', [10])
  8.   .then(function (result) {
  9.     console.log('Result: ' + result); // outputs 55
  10.   })
  11.   .catch(function (err) {
  12.     console.error(err);
  13.   })
  14.   .then(function () {
  15.     pool.terminate(); // terminate all workers when done
  16.   });

  17. // or run registered functions on the worker via a proxy:
  18. pool
  19.   .proxy()
  20.   .then(function (worker) {
  21.     return worker.fibonacci(10);
  22.   })
  23.   .then(function (result) {
  24.     console.log('Result: ' + result); // outputs 55
  25.   })
  26.   .catch(function (err) {
  27.     console.error(err);
  28.   })
  29.   .then(function () {
  30.     pool.terminate(); // terminate all workers when done
  31.   });
  32. ```

Worker can also initialize asynchronously:

myAsyncWorker.js

  1. ```js
  2. define(['workerpool/dist/workerpool'], function (workerpool) {
  3.   // a deliberately inefficient implementation of the fibonacci sequence
  4.   function fibonacci(n) {
  5.     if (n < 2) return n;
  6.     return fibonacci(n - 2) + fibonacci(n - 1);
  7.   }

  8.   // create a worker and register public functions
  9.   workerpool.worker({
  10.     fibonacci: fibonacci,
  11.   });
  12. });
  13. ```