Written by Rohit Pal

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.

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.

Basic Introduction to API

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.

Inter-Thread Communication

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...