In Node.js, worker threads come handy when doing large JavaScript tasks. Worker makes it simple to run Javascript code in parallel using threads, making it considerably faster and more efficient. We can complete difficult jobs without disrupting the main thread. Worker threads were not available in earlier Node versions. 

It is also in charge of transferring ArrayBuffer instances to handle CPU-intensive operations.

Node.js worker threads have proven to be the greatest solution for CPU performance because of the following features:

  • It runs a single process with multiple threads.
  • Runs single JS Engine instance per thread.
  • Executes one event loop per thread.
  • Executes single Node.js instance per thread.

To get started, we must update our Node.js to a newer version. 

Here's How to Land a Top Software Developer Job

Full Stack Developer - MERN StackExplore Program
Here's How to Land a Top Software Developer Job

History of CPU Intensive Applications in Node.js

There were several ways to run CPU-intensive apps in Node.js before worker threads. Some of them are mentioned here:

  • Run CPU-intensive programs in a child process with the child process module.
  • To conduct many CPU-intensive actions in multiple processes, use the cluster module.
  • Use a third-party module like Napa.js from Microsoft.

However, none of these alternatives were extensively implemented due to performance restrictions, increased complexity, lack of adoption, instability, and documentation.

Using Worker Threads for CPU Intensive Operations

Worker thread is an excellent solution to JavaScript's concurrency problem; it does not provide multi-threading facilities to the language. Instead, the worker threads approach allows applications to use several isolated JavaScript workers, with Node providing communication between workers and the parent worker. Each worker in Node.js has its own V8 and Event Loop instance. Workers, unlike children's processes, can exchange memory.

Example:

const { Worker } = require('worker_threads')

const runService = (WorkerData) => {

    return new Promise((resolve, reject) => {

        const worker = new Worker('./workerExample.js', { WorkerData });

        worker.on('message', resolve);

        worker.on('error', reject);

        worker.on('exit', (code) => {

            if (code !== 0)

                reject(new Error(`stopped with  ${code} exit code`));

        })

    })

}

const run = async () => {

    const result = await runService('hello node.js')

    console.log(result);

}

run().catch(err => console.error(err))

Add this to workerExample.js file.

const { WorkerData, parentPort } = require('worker_threads')

parentPort.postMessage({ welcome: WorkerData })

Output:

{ welcome: 'hello node.js' }

We imported Worker from worker threads in our main.js script, then passed the data (filename) for the worker to process. As seen in the workerExample.js service, the next step is to listen for message events from the worker thread. Our main application sends WorkerData to this worker service, which includes a means to transmit back processed data via parentPort. We utilize postMessage() on this object (parentPort) to deliver processed data.

Worker threads also allow us to share memory by using SharedArrayBuffer objects. Transferring ArrayBuffer objects can also be used to share memory.

Learn the Ins & Outs of Software Development

Caltech Coding BootcampExplore Program
Learn the Ins & Outs of Software Development

How Do Worker Thread Work?

Node.jsWorker

Worker threads were introduced as an experimental feature in Node V10. In version 12, it became stable. Because worker threads aren't a built-in component of Javascript, they don't operate exactly like a standard multi-threading system. It permits expensive tasks to be delegated to different threads rather than stopping the application's event loop.

A worker thread's job is to execute code that has been specified by the parent or main thread. Each worker operates independently. A worker and its parent, on the other hand, can communicate via a message channel. When Javascript doesn't enable multi-threading, worker threads employ a particular method to keep workers segregated from one another. Chrome's V8 engine is used to execute Node. V8 allows us to create isolated V8 runtimes. V8 Isolate are isolated instances with their own Javascript heaps and micro-task queues. A Node application has numerous Node instances running in the same process when workers are active. Although Javascript does not support concurrency by default, worker threads provide a workaround for running several threads in the same process.

What Distinguishes Worker Threads

  • To pass starting data, WorkerData is used. An arbitrary JavaScript value containing a clone of the data supplied to the Worker function Object() { [native code] } of this thread. As if using postMessage, the data is cloned ().
  • MessagePort is a communication port that allows many threads to communicate with each other. It can be used to send structured data, memory regions, and other MessagePorts from one worker to another.
  • Atomics is a tool that allows you to run several processes at the same time, saving time and allowing you to use JavaScript to add conditional variables.
  • MessageChannel is an asynchronous, two-way communication channel that can be used to communicate between threads.

How Do Node.js Workers Run in Parallel?

A V8 isolate is a standalone Chrome V8 runtime instance with its own JS memory and microtask queue. This allows each Node.js worker to run its JavaScript code entirely independently of the other workers. The disadvantage is that the workers are unable to immediately access each other's heaps.

Learn the Ins & Outs of Software Development

Caltech Coding BootcampExplore Program
Learn the Ins & Outs of Software Development

Crossing the JS/C++ Boundary

Using Node.js' APIs, the script is passed to V8. The JS code in the specified script is compiled by V8, and the assembly equivalent is returned. The produced assembly code is then run by Node.js using another V8 API. The code is compiled to assembly and copied to memory by the compiler.

To run the compiled code, it allocates a space on the memory, moves the code to the allocated space, and jumps to the memory space. Execution now starts from the compiled code at the jump. As a result, it has crossed a line. The code that is currently being run is JS code rather than C++ code. All of the pieces are now in place for assembly. When the compiled JS code is finished, it returns to the C++ code.  

The terms "C++ code" and "JS code" do not refer to the C++ or JS source code. It is the assembly code generated by the compiler from their source codes that is being executed to distinguish which assembly code is being executed.

Based on the above, we can split the worker setup process into two stages: 

  1. Initialization of the worker
  2. Running the worker

Initialization Step

  • The worker threads module in the Userland script is used to construct a worker instance.
  • The initialization script for Node's parent worker invokes C++ and produces an instance of an empty worker object. At this moment, the created worker is just a plain C++ object that hasn't begun yet.
  • When a C++ worker object is created, it assigns itself to a thread and generates a thread ID.
  • When the worker object is formed, the parent worker creates an empty initialization message channel.
  • The worker initialization script creates a public JS message channel. This is the message channel that the userland JS uses to send messages between the parent and the child worker.
  • The initialization metadata for the worker execution script is written to the IMC by the node parent worker initialization script, which calls into C++.

Running Step

The initialization process is now complete. The worker thread is then started by the worker initialization script, which calls into C++.

  • The worker is given a new V8 isolation to work with. A V8 isolate is a V8 runtime instance that runs on its own. The execution context of the worker thread is thus segregated from the rest of the application code.
  • libuv has been set up. This allows the worker thread to run its own event loop separate from the rest of the program.
  • The worker's execution script is performed, and the event loop for the worker is initiated.

  • Execution of workers: 

  1. The script uses C++ to access the IMC's initialization metadata.
  2. The worker execution script runs the file that will be used by the worker. Worker-simple in our case, is instance.js.

Getting the Best Out of Worker Threads

We now know how Node.js Worker Threads function. Understanding how they work can help us achieve the greatest results from worker threads. When creating more complex apps than worker-simple.js, we must keep in mind the following two primary worker thread considerations.

  • Even though worker threads are more lightweight than actual processes, spawning workers requires significant effort and might be costly if done frequently.
  • Using worker threads to parallelize I/O operations is not cost-effective because using Node.js native I/O techniques is relatively faster than establishing a worker thread from scratch solely to do that.

Here's How to Land a Top Software Developer Job

Full Stack Developer - MERN StackExplore Program
Here's How to Land a Top Software Developer Job

Worker Thread Pooling

In Node.js, worker thread pools is a collection of active worker threads that can be used to complete an incoming job. When a new task is received, it can be assigned to a worker who is available. Once the worker has completed the task, the result can be returned to the parent, and the worker is again available to accept new assignments. Thread pooling, when properly implemented, can dramatically increase speed by reducing the overhead of spawning new threads. It's also worth noting that establishing a high number of threads is inefficient because the number of parallel threads that can be properly run is always restricted by hardware.

Conclusion

In April of 2019, Node.js v12 was launched. Worker Threads, which were enabled by default in this version, were now supported (rather than needing an optional runtime flag). It's never been easier to use several CPU cores in Node.js applications!

This capability can be used by Node.js applications with CPU-intensive workloads to reduce execution time. This is especially important for Node.js serverless functions because serverless platforms charge based on execution time. Using several CPU cores results in enhanced performance as well as decreased costs.

Learn the Ins & Outs of Software Development

Caltech Coding BootcampExplore Program
Learn the Ins & Outs of Software Development

Workers (threads) are useful for doing JavaScript actions that require a lot of CPU power. They aren't very useful for tasks that require a lot of I/O. Asynchronous I/O operations built into Node.js are more efficient than Workers. Worker threads, unlike child process or cluster, can share memory. They achieve this via sharing SharedArrayBuffer instances or transferring ArrayBuffer objects. We hope that this article was able to give you a thorough knowledge of  Node Js worker it’s features and how we can use it in our software development projects.

If you are interested in learning more about NodeJS and other related concepts, you can enroll in Simplilearn’s exclusive Full Stack Web Development Certification Course and accelerate your career as a software developer. The program comprises a variety of software development courses, ranging from the fundamentals to advanced topics. 

Simplilearn also offers free online skill-up courses in several domains, from data science and business analytics to software development, AI, and machine learning. You can take up any of these courses to upgrade your skills and advance your career.

Our Software Development Courses Duration And Fees

Software Development Course typically range from a few weeks to several months, with fees varying based on program and institution.

Program NameDurationFees
Caltech Coding Bootcamp

Cohort Starts: 17 Jun, 2024

6 Months$ 8,000
Full Stack Developer - MERN Stack

Cohort Starts: 30 Apr, 2024

6 Months$ 1,449
Automation Test Engineer

Cohort Starts: 1 May, 2024

11 Months$ 1,499
Full Stack Java Developer

Cohort Starts: 14 May, 2024

6 Months$ 1,449