Node Worker Threads
What is it
worker_threads
is a module that enables the use of thread that executes JavaScript in parallel.
Although Node can handle several simultaneous requests very well, problems start to appear when your requests need to perform intensive operations that use a lot of the CPU time. By default, your node application run in a single thread, and CPU-heavy methods can block Node main loop and prevent it from answering other users' requests and cause them to wait for the operation to finish before their requests can be processed.
In this scenario, a good way to solve the problem is to offload the CPU-heavy processing to other threads, using the worker_threads
module.
When using Workers
the process changes slightly:
How to use it
To use the final worker_threads
API you need to use Node 12 LTS or newer. Its API consists in three main classes:
- Worker: Class used to instantiate a new thread that will run your script;
- MessageChannel: Used to create a communication channel between the instantiated threads; and
- MessagePort: Used to send and receive events and messages to other threads.
Here is a simple example of the use of the threads:
// index.js
import { Worker } from 'worker_threads';
export default processAsync = (script) => {
return new Promise((resolve, reject) => {
// Instantiate a new thread executing the 'worker.js' file
const worker = new Worker('worker.js', {
workerData: script
});
// Setup the listeners using the default 'MessagePort' instance
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0)
reject(new Error(`Worker stopped with exit code ${code}`));
});
});
};
// worker.js
import { parentPort, workerData} from 'worker_threads';
import { process } from 'some-cpu-heavy-processing-library';
const script = workerData;
parentPort.postMessage(parse(script));
Note that by default, when instantiating a worker, it creates one MessageChannel
between the parent and child thread. Its MessagePorts
are available through worker
in the parent thread and parent port
in the instantiated thread.
You can easily create new MessageChannels
:
const { port1, port2 } = new MessageChannel();
port1.on('message', (message) => console.log('received', message));
port2.postMessage({ foo: 'bar' });
You can also pass new ports to existing threads:
worker1 = new Worker(...);
worker2 = new Worker(...);
const { port1, port2 } = new MessageChannel();
worker1.postMessage({ port: port1 }, [port1]);
worker2.postMessage({ port: port2 }, [port2]);
How we use it today
We are currently using the worker_threads
module to offload the cpu heavy operations from the main thread of our application. The Phormar project has an implementation of a wrapper around the worker_threads to handle thread reinitialization, error handling, and communication. You can see it here.