como-trabajar-con-threads-nodejs

How to work with Threads in Node.js

  • 5 min

Worker threads provide a way to execute code concurrently in Node.js, allowing CPU-intensive tasks to be performed efficiently without blocking the main execution thread.

The worker_threads module in Node.js provides an API for creating and managing worker threads. Each worker thread runs its own JavaScript context and communicates with the main thread via messages.

Examples of using the worker_threads module

Basic Example

Let’s start with a basic example of how to use worker_threads to perform tasks in parallel:

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

// Check if the current thread is the main thread
if (isMainThread) {
  // If it's the main thread, define a set of data
  const data = 'some data';
  // Create a new worker thread and pass the defined data
  const worker = new Worker(import.meta.filename, { workerData: data });
  // Listen for messages sent from the worker thread
  worker.on('message', msg => console.log('Reply from Thread:', msg));
} else {
  // If not the main thread, get the data passed to the worker thread
  const source = workerData;
  // Convert the text to uppercase and then encode it to base64
  parentPort.postMessage(btoa(source.toUpperCase()));
}
Copied!

This example shows how we can use worker_threads to perform tasks in a separate worker thread and communicate with the main thread via messages.

Example: Calculating Prime Numbers in Parallel

Suppose we want to calculate prime numbers in a large range efficiently using worker threads.

We can split the range into several sub-ranges and assign a worker thread to calculate the primes in each sub-range.

// primeCalculator.js
import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';

function isPrime(num) {
  if (num <= 1) return false;
  if (num <= 3) return true;
  if (num % 2 === 0 || num % 3 === 0) return false;
  let i = 5;
  while (i * i <= num) {
    if (num % i === 0 || num % (i + 2) === 0) return false;
    i += 6;
  }
  return true;
}

if (isMainThread) {
  const min = 2;
  const max = 100000;
  const numThreads = 4;
  const range = Math.ceil((max - min) / numThreads);

  for (let i = 0; i < numThreads; i++) {
    const start = min + range * i;
    const end = i === numThreads - 1 ? max : start + range;
    const worker = new Worker(__filename, {
      workerData: { start, end },
    });
    worker.on('message', (message) => {
      console.log('Primes found:', message.primes);
    });
  }
} else {
  const { start, end } = workerData;
  const primes = [];
  for (let i = start; i < end; i++) {
    if (isPrime(i)) {
      primes.push(i);
    }
  }
  parentPort.postMessage({ primes });
}
Copied!

In this example,

  • The main thread (if isMainThread is true) splits the number range (min to max) into sub-ranges for several worker threads.
  • Each worker thread (Worker) receives a sub-range to calculate prime numbers within that range.
  • When a Worker finds prime numbers, it sends those primes back to the main thread using parentPort.postMessage.

When running this script (node primeCalculator.js), you will see the prime numbers found in each sub-range printed to the console.

Example: Parallel Image Processing

Suppose we have an application that needs to process a large number of images efficiently.

We can use worker threads to distribute image processing in parallel, which would significantly speed up processing time.

import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
import { loadImage, processImage, saveImage } from './imageUtils.js';

if (isMainThread) {
  const imagePaths = [
    'image1.jpg',
    'image2.jpg',
    'image3.jpg',
    // Add more image paths as needed
  ];

  const numThreads = 4;
  const imagesPerThread = Math.ceil(imagePaths.length / numThreads);

  for (let i = 0; i < numThreads; i++) {
    const start = i * imagesPerThread;
    const end = start + imagesPerThread;
    const paths = imagePaths.slice(start, end);

    const worker = new Worker(__filename, {
      workerData: { paths },
    });

    worker.on('message', ({ processedImages }) => {
      console.log('Image processing complete:', processedImages);
    });
  }
} else {
  const { paths } = workerData;
  const processedImages = [];

  paths.forEach(async (path) => {
    try {
      const image = await loadImage(path);
      const processedImage = await processImage(image);
      const savedPath = await saveImage(processedImage);
      processedImages.push(savedPath);
    } catch (error) {
      console.error('Error processing image:', error);
    }
  });

  parentPort.postMessage({ processedImages });
}
Copied!

Explanation of the example,

  • The main thread (if isMainThread is true) loads the paths of the images to be processed and divides these paths into subsets for each worker thread.
  • Each worker thread (Worker) receives a set of image paths to process in parallel.
  • Inside each worker thread, each image is loaded, processed, and saved. The final result (the path of the processed images) is sent back to the main thread via messages.
  • In the end, the main thread receives the results from all worker threads and displays the completion message.

A simple example, but one that shows us how we can use worker threads to perform intensive tasks in parallel, such as image processing.

Download the Code

All the code from this post is available for download on Github github-full