Simple Implemention to Understand worker_threads in NodeJS

Simple Implemention to Understand  worker_threads in NodeJS
Photo by Olivier Collet / Unsplash

One of the most highly request features in the NodeJS runtime environment is being able to use multiple cores — a lot of programmers don't exactly understand the availability of NodeJS packages available to use to help with this issue. The package is worker_threads. This package really does help fix the issue.

Right below is an everyday code example without using worker_threads.

const now = Date.now();

const arr = [];
for(let i = 0; i < 10000000; i++){
    arr[i] = Math.random()
}

const createArrayTime = Date.now() - now;

let sum = 0;
let numberBefore = 1;
for(let i = 0; i < arr.length; i++){
    sum = sum + arr[i] / numberBefore;
    numberBefore = arr[i];
}

const addArrayTime = Date.now() - now;

console.log(createArrayTime, addArrayTime)

Breaking Down the Example

  • The code starts with creating an array with 10 million random numbers, it calculates the time for doing this.
  • It then loops the array and adds all numbers together, but each number added is divided by the number before. This is done with the numberBefore variable, it calculates the time for doing all of this.
  • The code then logs to console the time it took in milliseconds.

The Results

> node index.js
296 500
> exit

With us running the code, it only took approximately 800 milliseconds to run. For such a simple application, that is quite slow. We are able to speed up this application code to just an average of 100 milliseconds. This is a major performance gain with some code refactoring.

What is worker_threads and how do you use it?

  • worker_threads allows you to send what code each thread of a CPU core should run.
  • The workers (each thread) are able to communicate with each other and the main code.
  • They can also be used to run multiple files at the same time.

Learning how to properly use worker_threads could be an extremely important tool for building anything with NodeJS due to the benefits.

How do I use worker_threads?

Start with the basics, declaring your imports.

import { parentPort, isMainThread, Worker } 
  from 'node:worker_threads';
import os from 'node:os';

Bringing up the issues we had before with performance we had earlier, the goal is to speed up and make the small application of code above to simply run faster. Since as we all know NodeJS runs everything on a single core – this is by default also, we can change this behavior to use multi-threading.

Bringing our array back without the use of worker_threads to create it will be more clear. Here is a code snippet of how we are going to do this.

const arr = [];
for(let i = 0; i < 10000000; i++){
    arr[i] = Math.random()
}

Now that we have an input, we need to split the input into different blocks. This is because the worker_threads module does not automatically assign a task to each thread and we have to assign it ourselves. This gives more functionality.

function chunkify(array, amnt){
  const chunks = [];
  const chunkSize = Math.ceil(array.length, amnt);
  for(let i = 0; i < array.length; i++){
    chunks.push(array.slice(i, i + chunkSize);
  }
}

We now can use both the os and worker_threads to make the most out of our code. Since we have the function needed to split our input, we are able to make each thread run a certain chunk of the code.

We can use isMainThread to identify whether or not we are running as a worker or a MainThread.

if(isMainThread){
  // stuff...
  let results = [];
  async function run(chunks){
    const promises = [];
    for(let i = 0; i < chunks.length; i++){
      const worker = new Worker('./index.js');
      const promise = new Promise((resolve, reject) => {
        worker.on('message', (message) => {
          resolve(message);
          results.push(message);         
        });
        worker.postMessage(chunks[i]);
      });
      promises.push(promise);
    }
    await Promise.all(promises);
  }
    
  run(chunks, os.cpus().length - 1); // Leave 1 core.
  
} else {
  // ...
  

We now have a run function to use for workers.

  • The code will re-run index.js, but not as the MainThread, but rather the worker.
  • The code will then execute everything in the else block, this will allow the split inputs to be executed by each thread.
  • It will then resolve the promise, which will then add message (a message from the worker to the Main Thread) to a result variable.

Now, in the worker code, we need to learn how to recieve and send messages from each worker.

else {
 
  // We are a worker.
  
  function getMessage(){
    return new Promise((resolve, reject) => {
      parentPort.on('message', (message) => {
        resolve(message);
      })
    })
  }

  const inputToProcess = getMessage();
  const processedInput = someFunction(inputToProcess);
  
  function sendMessage(){
    parentPort.postMessage(processedInput);
  }

  // We're done here.
  parentPort.close();
    
}

Step 5: Complete the worker part of your code.

And voila! Now we have our code working with multiple threads in JavaScript! The results of these code changes are right below.

> node index.js
297 102
> exit

Results

The Analysis

The results are as to be expected. The first number has not changed since we did not use any workers to make our input, while the second number is just the time of the code from when the workers were just being executed to the end. This excludes using functions like chunkify or with os grabbing CPU counts.

The Conclusion

JavaScript Node.js is one of the most powerful coding languages and environments in the world for backend applications. Being able to use worker_threads and seeing amazing results from them really makes it even better.


Do you like what you're reading from the CoderOasis Technology Blog? We recommend reading our Implementing RSA in Python from Scratch series next.
Implementing RSA in Python from Scratch
Please note that it is essential for me to emphasize that the code and techniques presented here are intended solely for educational purposes and should never be employed in real-world applications without careful consideration and expert guidance. At the same time, understanding the principles of RSA cryptography and exploring various

The CoderOasis Community

Did you know we have a Community Forums and Discord Server? which we invite everyone to join us? Want to discuss this article with other members of our community? Want to join a laid back place to chill and discuss topics like programming, cybersecurity, web development, and Linux? Consider joining us today!