Language: EN

como-trabajar-con-threads-nodejs

How to work with Threads in Node.js

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 to create and manage worker threads. Each worker thread executes 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()));
}

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 via messages.

Example: Calculating prime numbers in parallel

Let’s say 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 range of numbers (min to max) into sub-ranges for several worker threads.
  • Each worker thread (Worker) 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 you run this script (node primeCalculator.js), you’ll see the prime numbers found in each sub-range printed to the console.

Example: Image processing in parallel

Let’s say we have an application that needs to efficiently process a large number of images.

We can use worker threads to distribute the 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 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 (Worker) 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 via messages.
  • In the end, the main thread receives the results from all the worker threads and displays the processing complete message.

In a simple example, but it 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