Multi-threading with NodeJS (Worker Threads)
Did you know you can perform multi-threading in NodeJS? NodeJS’ implementation of multi-threading is called worker_threads
which functions a bit differently to other multi-threaded technologies but can be useful in certain scenarios to improve your application’s performance.
Prerequisites
A basic understanding of Javascript and NodeJS.
What is Multi-threading?
Multi-threading is the technique of running tasks on parallel threads, each thread running on its own CPU core.
In a single threaded application, tasks are executed one after the other, in serial. Intensive tasks, like reading a file, block the single thread, keeping the other tasks waiting and slowing down performance.
Multi-threading in NodeJS
Multi-threading with NodeJS’ worker_threads
does not work like most multi-threaded technologies, in that each thread is isolated and does not share memory with any other thread. Threads are however able to communicate with each other and the main process through message posting (pub-sub).
worker_threads
are useful for CPU intensive tasks such as working with large datasets and performing calculations. They are however not useful for I/O operations like working with files, making HTTP requests or querying a database. But don’t worry, NodeJS already runs I/O operations on parallel threads under the hood without us needing to do anything!
There is also a performance overhead with creating new worker threads as NodeJS spins up a new runtime for each thread. Light-weight tasks ran in worker threads will be slower than if they were ran on the main process, so use worker_threads
for intensive tasks.
Knowing these caveats to NodeJS’ multi-threading, ensure that you use it for the correct situation. Otherwise it can have the opposite of the intended effect and worsen performance.
Using Worker Threads
Each task that you want to perform in a parallel thread will be a separate Javascript file. This will be the code run by the worker thread.
1. Create a Worker Thread
Create a worker thread by instantiation a Worker
, providing the path to the JS worker file. Data can be sent to the worker via the options parameter { workerData: ... }
// In some service or app.js
import { Worker } from 'worker_threads'
const worker = new Worker(<path-to-worker-file>, { workerData: data })
2. Listen for Worker Result
The worker posts different types of events which can be listened for:
- message – the result of the worker thread. Contains any data the worker returned
- error – returns with the error thrown inside the worker
- exit – posted on worker thread exit/termination
// In some service or app.js
import { Worker } from 'worker_threads'
const worker = new Worker(<path-to-worker-file>, { workerData: data })
worker.on('message', (result) => { ... })
worker.on('error', (error) => { ... })
worker.on('exit' (exit => { ... })
3. The Worker File
The worker file is a Javascript file, which will be executed when the worker thread is created.
It can return data to the main thread by calling parentPort.postMessage(<data>)
.
Ensure that you call process.exit() at the end of your code so that the worker thread can be terminated and resources can be freed.
// worker-file-name.js
import { isMainThread, parentPort, workerData } from 'worker_threads'
// This ensures the code only runs from within a worker thread
if (!isMainThread) {
... // CPU intensive operations
parentPort.postMessage(<data>)
process.exit()
}
Conclusion
Now you know how to multi-thread NodeJS! Pretty simple right? The next time you are transforming large datasets or running lots of calculations, consider worker_threads
to offload the job to another thread.