Worker Threads in NodeJS
NodeJS for its design engine was preferred to be used as an I/O performant and not as a CPU performant backend.
NodeJS for its design engine was preferred to be used as an I/O performant and not as a CPU performant backend.
Created by Ryan Dahl NodeJS first came into being in May 27, 2009, NodeJS is traditionally seen as a single threaded asynchronous engine. Single threaded async engine created from Chrome V8's engine that could perform as a backend engine.
For its design, the V8 engine was preferred to be used as an I/O performant and not as a CPU performant backend. However, NodeJS Team has been working towards making NodeJS to work with threads too.
Even in Web Browsers, Web Worker API provides a way to create background task that could be run simultaneously from browser window
scope so that it wouldn't block single-threaded engines when we would like to perform a CPU bound task. We would discuss Web Workers are a topic to be discussed in future posts. Let's get back to Worker Threads.
First introduced to NodeJS in version v10.5.0
with experimental feature, Worker Threads, with version v12 LTS
API became stable and ready to use in production.
Worker Threads can be imported to NodeJS as simple as
const worker = require('worker_threads');
And then, create a Worker Thread Object as
const worker = new Worker(__filename)
The Worker
class represents an independent JavaScript execution thread. The __filename
can be the same javascript file or a target javascript file to load with new thread
created.
The Worker
Object also accepts other values in its constructor one of which is workerData
. Let's see an example of a script that would create a new thread from its main thread and can process initialised data via workerData passed inside the constructor.
const {
Worker, isMainThread, parentPort, workerData
} = require('worker_threads');
if (isMainThread) {
const worker = new Worker(__filename, {
workerData: content
});
} else {
const { lib } = require('some-js-library');
// workerData available to be processed
}
isMainThread
tells the code that if the code is running in main thread or not. Here, we have created the worker instance and passed some content to its constructor via workerData
. Since worker is create with same file location it would be also be loaded via Worker Thread inside a new thread and pass content data would be available to us in thread via workerData
value imported.
Two-way communication can be achieved through inter-thread message passing. Worker Threads supported MessageChannel
which exposes MessagePort
objects as port1
and port2
for sending and receiving. Here is a code snippet.
const assert = require('assert');
const {
Worker, MessageChannel, MessagePort, isMainThread, parentPort
} = require('worker_threads');
if (isMainThread) {
const worker = new Worker(__filename);
const channel = new MessageChannel();
worker.postMessage({ hereIsYourPort: channel.port1 }, [channel.port1]);
channel.port2.on('message', (value) => {
console.log('received:', value);
});
} else {
parentPort.once('message', (value) => {
assert(value.hereIsYourPort instanceof MessagePort);
value.hereIsYourPort.postMessage('the worker is sending this');
value.hereIsYourPort.close();
});
}
The API is very simple and easy to understand on its own. Since concept of threads comes with a lot of caveats we would discuss those in following blog posts.
Next, we would analyse a use case for task break down into threads and see how we can improve performance of certain CPU intensive tasks via Worker Threads in NodeJS.
Stay Tuned...