Using Worker Threads in Node.js

Welcome back! Let's dive into how we can use worker threads in Node.js to perform parallel operations efficiently.

Setting Up Worker Threads

Step 1: Create a Worker Threads Example File

Start by creating a folder, ThreadsExample, and within it, create a file called threads-dogs.js.

Step 2: Importing the Worker Threads Module

To use worker threads, require the worker_threads module and destructure the necessary values from it:

const { Worker, isMainThread, workerData } = require('worker_threads');

Step 3: Checking the Execution Context

The isMainThread value helps us determine if the current code is running in the main thread or a worker thread. Using this, we can create new worker threads conditionally:

if (isMainThread) {
    console.log(`Main thread has process ID: ${process.pid}`);

    // Creating two worker threads
    new Worker(__filename, { workerData: [7, 6, 2, 3] });
    new Worker(__filename, { workerData: [1, 3, 4, 3] });
} else {
    console.log(`Worker thread has process ID: ${process.pid}`);
}

Here, __filename is a Node.js global variable that refers to the current file path. We pass it to the Worker constructor so that each worker runs the code within the same file.

Step 4: Avoid Infinite Worker Creation

To prevent recursive worker creation, ensure new workers are created only in the main thread by checking isMainThread.

Demonstrating Shared Process ID

Unlike the cluster module, which spawns separate processes, worker threads run within the same process. To see this:

if (isMainThread) {
    console.log(`Main thread has process ID: ${process.pid}`);
    new Worker(__filename, { workerData: [7, 6, 2, 3] });
    new Worker(__filename, { workerData: [1, 3, 4, 3] });
} else {
    console.log(`Worker thread has process ID: ${process.pid}`);
}

Running this code will output identical process IDs for the main thread and all worker threads.

Performing Parallel Operations

Now let's assign some work to the worker threads. Suppose we have arrays of numbers to sort. Sorting is a CPU-intensive operation, which makes it a great use case for worker threads.

Passing Data to Worker Threads

The Worker constructor accepts an object as a second parameter, where the workerData property allows us to send data to the worker:

if (isMainThread) {
    new Worker(__filename, { workerData: [7, 6, 2, 3] });
    new Worker(__filename, { workerData: [1, 3, 4, 3] });
} else {
    console.log(`Worker data: ${workerData}`);

    // Sorting the array
    const sortedData = workerData.sort((a, b) => a - b);
    console.log(`Sorted data: ${sortedData}`);
}

Output

When you run the program using node threads-dogs.js, you’ll see output like this:

Main thread has process ID: 12345
Worker data: [7, 6, 2, 3]
Sorted data: [2, 3, 6, 7]
Worker data: [1, 3, 4, 3]
Sorted data: [1, 3, 3, 4]

Explanation

  • The main thread sends an array to each worker thread using workerData.

  • Each worker processes the data independently by sorting the array.

  • The sorted arrays are logged to the console.

Key Advantages

  1. Parallel Execution: Each worker thread runs independently, taking advantage of multiple CPU cores.

  2. Shared Memory: All threads share the same process memory, reducing overhead compared to spawning separate processes.

  3. Efficient Resource Utilization: Worker threads provide a more efficient alternative to clustering for non-server-related tasks.

Summary

Using worker threads in Node.js allows us to execute JavaScript in parallel, optimizing CPU-bound tasks. While they’re not a replacement for clusters in server scenarios, worker threads are perfect for tasks like sorting, data processing, and mathematical computations.