Lesson 30 of 50 – Worker Threads

60% Completed
60%

Worker Threads in Node.js

Worker Threads allow Node.js to execute JavaScript in additional threads. They are especially useful for CPU-intensive tasks that would otherwise block the main JavaScript thread.

Remember: Worker Threads are different from Child Processes. Workers run in separate JavaScript threads within the same process, while child processes run as separate operating-system processes.

1. What Are Worker Threads?

Node.js normally executes JavaScript on a single main thread. Long-running calculations can block that thread and delay other callbacks. The built-in worker_threads module allows you to move suitable calculations to worker threads.

Worker Threads are useful for:

  • Large mathematical calculations.
  • Data processing and transformation.
  • CPU-intensive algorithms.
  • Tasks that can be divided into independent pieces of work.
Main Thread and Worker Thread
Main Thread
    |
    | Starts a worker
    v
Worker Thread
    |
    | Performs CPU-intensive work
    v
Sends result back to Main Thread

The main thread can continue handling other JavaScript work while the worker performs its calculation.

2. Importing the Worker Threads Module

The worker_threads module is built into Node.js. You do not need to install an additional npm package.

Example: Import Worker
const { Worker, isMainThread } = require("worker_threads");

console.log("Main thread:", isMainThread);
Output:
Main thread: true

The isMainThread property is true when the code is running on the main thread and false inside a worker.

3. Creating Your First Worker

A worker can execute a separate JavaScript file. In this example, the main file starts a worker and receives a message from it.

Step 1: Create worker.js
const { parentPort } = require("worker_threads");

parentPort.postMessage("Hello from Worker Thread!");
Step 2: Create main.js
const { Worker } = require("worker_threads");

const worker = new Worker("./worker.js");

worker.on("message", function(message) {
    console.log(message);
});

worker.on("error", function(error) {
    console.log("Worker error:", error.message);
});

worker.on("exit", function(code) {
    console.log("Worker stopped with code:", code);
});
Example Output:
Hello from Worker Thread!
Worker stopped with code: 0

Save both files in the same directory and run node main.js.

4. Sending Data to a Worker

You can pass data to a worker using the workerData option. The worker receives the supplied data through the workerData property.

Example: Passing a Number

Create worker.js:

const { parentPort, workerData } = require("worker_threads");

const result = workerData * workerData;

parentPort.postMessage(result);

Create main.js:

const { Worker } = require("worker_threads");

const worker = new Worker("./worker.js", {
    workerData: 5
});

worker.on("message", function(result) {
    console.log("Square:", result);
});

worker.on("error", function(error) {
    console.log("Error:", error.message);
});
Output:
Square: 25

5. Sending Messages Between Threads

A worker can send messages to the main thread using parentPort.postMessage(). The main thread can send messages back using worker.postMessage().

Example: Two-Way Communication

Create worker.js:

const { parentPort } = require("worker_threads");

parentPort.on("message", function(number) {
    parentPort.postMessage(number * 2);
});

Create main.js:

const { Worker } = require("worker_threads");

const worker = new Worker("./worker.js");

worker.on("message", function(result) {
    console.log("Result:", result);
    worker.terminate();
});

worker.on("error", function(error) {
    console.log("Error:", error.message);
});

worker.postMessage(10);
Output:
Result: 20

The main thread sends 10 to the worker, and the worker returns 20. The main thread then terminates the worker.

6. Performing CPU-Intensive Calculations

Worker Threads are most useful when calculations take significant CPU time. The following example calculates the sum of numbers from 1 to a supplied limit in a separate worker.

Example: Sum Numbers Using a Worker

Create sumWorker.js:

const { parentPort, workerData } = require("worker_threads");

let sum = 0;

for (let i = 1; i <= workerData; i++) {
    sum += i;
}

parentPort.postMessage(sum);

Create main.js:

const { Worker } = require("worker_threads");

const worker = new Worker("./sumWorker.js", {
    workerData: 1000000
});

worker.on("message", function(result) {
    console.log("Sum:", result);
});

worker.on("error", function(error) {
    console.log("Error:", error.message);
});
Output:
Sum: 500000500000

The worker performs the calculation and sends the final result to the main thread.

7. Handling Worker Errors

Workers can fail because of exceptions or other runtime problems. Use the worker's error event to handle uncaught errors in the worker.

Example: Worker Error

Create errorWorker.js:

throw new Error("Something went wrong inside the worker");

Create main.js:

const { Worker } = require("worker_threads");

const worker = new Worker("./errorWorker.js");

worker.on("error", function(error) {
    console.log("Worker failed:", error.message);
});

worker.on("exit", function(code) {
    console.log("Worker exited with code:", code);
});
Example Output:
Worker failed: Something went wrong inside the worker
Worker exited with code: 1

An uncaught exception causes the worker to emit an error and exit with a nonzero code.

8. Worker Threads vs Child Processes

Feature Worker Threads Child Processes
Execution Separate JavaScript threads Separate operating-system processes
Best suited for CPU-intensive JavaScript External programs and process isolation
Communication Messages and shared memory options Pipes, IPC, or other process communication
Memory Can share selected memory through SharedArrayBuffer Normally separate process memory
Module worker_threads child_process

9. When Should You Use Worker Threads?

Common Use Cases
  • Image processing and data transformations.
  • Large mathematical calculations.
  • CPU-intensive algorithms.
  • Processing large datasets in independent tasks.
  • Keeping long calculations from blocking the main JavaScript thread.
Important: Worker Threads have setup and communication costs. They are usually not necessary for simple tasks or ordinary asynchronous file and network I/O. For CPU-intensive workloads, consider reusing workers through a worker pool rather than creating a new worker for every small task.

10. Practical Assignment

Practice these programs:
  1. Create a worker that sends a greeting to the main thread.
  2. Pass a number using workerData and calculate its square.
  3. Send a message from the main thread to a worker.
  4. Calculate the sum of numbers using a worker.
  5. Handle an error thrown inside a worker.
  6. Compare Worker Threads with Child Processes.
  7. Create a worker that receives several numbers and returns their sum.

Test Your Knowledge – Worker Threads

Select the correct answer for each question.

1. Which Node.js module provides Worker Threads?
2. Which method sends a message from a worker to its parent?
3. Which type of work is best suited to Worker Threads?