DEV Community

Ish Thumber
Ish Thumber

Posted on

Thread Management in Node.js

Many people are curious about single-threaded Node. Js is capable of competing with multithreaded back ends. To understand why, we must first grasp what we mean when we claim Node is single-threaded.

Node.js made it possible for developers to use the language to write back-end code.

Back-end languages that support multithreading in general have a variety of ways for syncing values between threads and other thread-oriented features. Adding similar capability to JavaScript would necessitate a complete rewrite of the language, which was not Dahl's intention. He had to devise a workaround in order for simple JavaScript to allow multithreading. Let us investigate.


  • How Node.js really works

Node.js uses two kinds of threads: a main thread handled by event loop and several auxiliary threads in the worker pool.

The event loop is the mechanism that accepts callbacks (functions) and registers them to be run at a later time. It runs in the same thread as the JavaScript code. The event loop is also halted when a JavaScript operation blocks the thread.

Worker pools are an execution paradigm that launches and manages independent threads, which then complete the work synchronously and deliver the result to the event loop. The event loop then calls the specified callback and returns the result.

In short, it takes care of asynchronous I/O operations — primarily, interactions with the system’s disk and network. It is mainly used by modules such as fs (I/O-heavy) or crypto (CPU-heavy). Worker pool is implemented in libuv, which results in a slight delay whenever Node needs to communicate internally between JavaScript and C++, but this is hardly noticeable.

fs.readFile(path.join(__dirname, './package.json'), (err, data) => {
    if (err) {
        return null;
    }
    console.log(data.toString());
});
Enter fullscreen mode Exit fullscreen mode

In above example we don’t have to wait synchronously for something to happen. We tell the worker pool to read the file and call the provided function with the result. Since worker pool has its own threads, the event loop can continue executing normally while the file is being read.


  • Introducing: worker_threads

The worker_threads module is a package that allows us to create fully functional multithreaded Node.js applications.

A thread worker is a piece of code (usually taken out of a file) spawned in a separate thread.

App Thread

Worker Threads in Node.js are helpful for intensive JavaScript processes. Worker's use of threads allows it to perform JavaScript code in parallel, making it significantly faster and more efficient. We can do difficult jobs without interfering with the main thread. Worker threads were not present in earlier versions of Node. As a result, before you begin, upgrade your Node.js.

Now create two files for implementing the thread as shown below:
Filename: worker.js

const { workerData, parentPort } = require('worker threads')

console.log('Technical Articles on ' + workerData);

parentPort.postMessage({ fileName: workerData, status: 'Done' });
Enter fullscreen mode Exit fullscreen mode

Here, the workerData and parentPort are part of Worker Thread. The workerData is used for fetching the data from the thread and parentPort is used for manipulating the thread. The postMessage() method is used for posting the given message in the console by taking the filename as fetched by workerData.

const { Worker } = require('worker_threads')

function runService(workerData) {
    return new Promise((resolve, reject) => {
        const worker = new Worker('./worker.js', { workerData });
        worker.on(' message', resolve);
        worker.on('error', reject);
        worker.on('exit',
            (code) => {
                if (code !== O)
                    reject(new Error(
                        `Stopped the Worker Thread with the exit code: ${code}`));
            })
    })
}

async function run() {
    const result = await runService('Node.js Thread Management')
    console.log(result);
}
run().catch(err => console.error(err))
Enter fullscreen mode Exit fullscreen mode

Here, the function runService() return a Promise and runs the worker thread. The function run() is used for calling the function runService() and giving the value for workerData.

Output:
Output


Here are the most common events:

worker.on('error', (error) => {});
Enter fullscreen mode Exit fullscreen mode

The error event is emitted whenever there’s an uncaught exception inside the worker. The worker is then terminated, and the error is available as the first argument in the provided callback.

worker.on('exit', (exitCode) => {});
Enter fullscreen mode Exit fullscreen mode

exit is emitted whenever a worker exits. If process.exit() was called inside the worker, exitCode would be provided to the callback. If the worker was terminated with worker.terminate(), the code would be 1.

worker.on('online', () => {});
Enter fullscreen mode Exit fullscreen mode

online is emitted whenever a worker stops parsing the JavaScript code and starts the execution. It’s not used very often, but it can be informative in specific cases.

worker.on('message', (data) => {});
Enter fullscreen mode Exit fullscreen mode

message is emitted whenever a worker sends data to the parent thread.


Useful properties available in the worker_threads module.

There are a few properties available inside the worker_threads module:

  • isMainThread

The property is true when not operating inside a worker thread. If you feel the need, you can include a simple if statement at the start of a worker file to make sure it is only run as a worker.

import { isMainThread } from 'worker_threads'

if (isMainThread) {
    throw new Error('It is not a worker');
}
Enter fullscreen mode Exit fullscreen mode
  • workerData

Data included in the worker’s constructor by the spawning thread.

const worker = new Worker(path, { workerData });
Enter fullscreen mode Exit fullscreen mode

In Worker thread:

import { workerData } from 'worker_threads';
console.log(workerData.property);
Enter fullscreen mode Exit fullscreen mode
  • parentPort

The previously mentioned MessagePort instance was used to communicate with the parent thread.

  • threadId

A unique identifier assigned to the worker.


in collaboration with
@shyamaljoshi
@ritu9902
@padmanabhkhunt
@sagar1209

Top comments (0)