Language: EN

como-trabajar-con-threads-nodejs

How to work with Threads in Node.js

The worker threads provide a way to run code concurrently in Node.js, allowing to perform CPU-intensive tasks efficiently and without blocking the main execution thread.

The worker_threads module in Node.js provides an API to create and manage worker threads. Each worker thread runs its own JavaScript context and communicates with the main thread through messages. This enables the execution of concurrent tasks.

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 is the main thread, a set of data is defined
  const data = 'some data';
  // A new worker thread is created and passed 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 it is not the main thread, the data passed to the worker thread is obtained
  const source = workerData;
  // The text is converted to uppercase and then encoded to base64
  parentPort.postMessage(btoa(source.toUpperCase()));
}

In this example, we see how we can use worker_threads to perform tasks in a separate worker thread and communicate with the main thread through messages.

Example: Calculating prime numbers in parallel

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

We can divide 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 });
}

In this example,

  • The main thread (if isMainThread is true) divides the number range (min to max) into sub-ranges for multiple worker threads.
  • Each worker thread receives a sub-range to calculate the 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: Image processing in parallel

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, significantly speeding up the 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 the image:', error);
    }
  });

  parentPort.postMessage({ processedImages });
}

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 receives a set of image paths to process in parallel.
  • Within 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 through messages.
  • In the end, the main thread receives the results from all worker threads and displays the message of the complete processing.

In a simple example, but it shows us how we can use worker threads to perform intensive tasks in parallel, such as image processing.