Worker Threads in Node.js are a feature that allows you to execute JavaScript code in parallel across multiple threads. This feature is particularly useful for performing CPU-intensive tasks that would otherwise block the main thread and degrade performance in a single-threaded, event-driven environment like Node.js.
Why Use Worker Threads?
Node.js is single-threaded, meaning that all code execution happens on a single thread. This is fine for I/O-bound operations because the Event Loop efficiently handles these operations asynchronously. However, for CPU-intensive tasks (like complex calculations, data processing, or image manipulation), the single-threaded nature of Node.js can become a bottleneck, as these tasks can block the Event Loop, causing delays in handling other tasks.
Worker Threads allow you to offload these heavy computations to separate threads, enabling better concurrency and responsiveness in your Node.js applications.
How Worker Threads Work
Worker Threads are part of the worker_threads
module, introduced in Node.js v10.5.0 (and stabilized in Node.js v12). Each worker thread runs in its own isolated JavaScript environment, meaning it has its own event loop, memory, and V8 instance. This isolation ensures that a blocking operation in one worker thread does not block the main thread or other worker threads.
Basic Example:
Step 1: Create a Worker Thread
Create a file named worker.js
that contains the code to be executed by the worker thread:
// worker.js
const { parentPort } = require('worker_threads');
// Perform a CPU-intensive task
let count = 0;
for (let i = 0; i < 1e9; i++) {
count += i;
}
// Send the result back to the main thread
parentPort.postMessage(count);
Step 2: Use the Worker in the Main Thread
Create a file named main.js
to spawn the worker and handle the results:
const { Worker } = require('worker_threads');
// Create a new worker thread
const worker = new Worker('./worker.js');
// Listen for messages from the worker thread
worker.on('message', (result) => {
console.log(`Result from worker: ${result}`);
});
// Listen for errors from the worker thread
worker.on('error', (err) => {
console.error(`Worker error: ${err}`);
});
// Listen for the worker thread to exit
worker.on('exit', (code) => {
if (code !== 0)
console.error(`Worker stopped with exit code ${code}`);
});
console.log('Main thread is still responsive...');
Step 3: Run the Main Thread
Execute the main.js
file:
node main.js
Output:
Main thread is still responsive...Result from worker: 499999999500000000
Explanation:
The main thread spawns a worker thread to perform a CPU-intensive calculation (summing numbers).
While the worker is busy, the main thread remains responsive and can handle other tasks.
Once the worker completes its task, it sends the result back to the main thread via a message.
When to Use Worker Threads?
CPU-Intensive Tasks: Use worker threads for tasks that involve heavy computation, such as image processing, video encoding, data compression, and mathematical calculations.
Parallel Processing: If you need to perform parallel processing, worker threads are an effective way to leverage multiple CPU cores.
Isolated Execution: When you want to run code in an isolated environment, worker threads ensure that memory and event loops are separated.
When Not to Use Worker Threads
I/O-Bound Tasks: For I/O-bound tasks, the asynchronous, non-blocking nature of Node.js is usually sufficient. Worker threads add complexity and overhead, so they are not needed in these cases.
Simple Applications: If your application does not involve CPU-intensive operations, worker threads may not provide a significant benefit.
Worker Threads Diagram
1 Event Loop per thread
1 JS Engine Instance per thread
1 Node.js Instance per thread
Since worker_threads make new threads inside the same process it requires fewer resources. Also, we are able to pass data between these threads because they have a shared memory space.