Node.js Worker Threads - True Multi-Threaded JavaScript Execution & CPU Offloading

For years, critics claimed Node.js was unsuitable for CPU-heavy tasks because of its single-threaded nature. Introduced natively in modern Node.js, the worker_threads module provides true multi-threaded JavaScript execution by running multiple Node.js event loops and V8 isolates inside a single OS process.

Multi-Threaded Prime Number Calculator

// primeWorker.js
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');

if (isMainThread) {
  // 1. MAIN THREAD LOGIC
  console.log('[Main Thread] Spawning background worker thread...');

  const worker = new Worker(__filename, {
    workerData: { start: 2, end: 10000000 } // Pass data to worker
  });

  // Receive messages from worker
  worker.on('message', (result) => {
    console.log(`[Main Thread] Found ${result.primeCount} primes in ${result.duration}ms!`);
  });

  worker.on('error', (err) => console.error('[Worker Error]', err));
  worker.on('exit', (code) => console.log(`Worker thread exited with code ${code}`));

  console.log('[Main Thread] Non-blocking! Can still accept incoming HTTP requests.');

} else {
  // 2. BACKGROUND WORKER THREAD LOGIC
  const { start, end } = workerData;
  const startTime = Date.now();

  function isPrime(num) {
    for (let i = 2, s = Math.sqrt(num); i <= s; i++) {
      if (num % i === 0) return false;
    }
    return num > 1;
  }

  let count = 0;
  for (let i = start; i < end; i++) {
    if (isPrime(i)) count++;
  }

  const duration = Date.now() - startTime;

  // Send result back to the main thread
  parentPort.postMessage({ primeCount: count, duration });
}

Shared Memory with SharedArrayBuffer & Atomics

Normally, message passing between threads serializes data (structured cloning). With SharedArrayBuffer, both threads read and mutate the exact same memory without any copy overhead:

// Allocate a 4-byte shared buffer for a 32-bit integer
const sharedBuffer = new SharedArrayBuffer(4);
const sharedArray = new Int32Array(sharedBuffer);

// Thread-safe atomic increment (prevents race conditions)
Atomics.add(sharedArray, 0, 1);
console.log('Thread-safe atomic value:', Atomics.load(sharedArray, 0));

Frequently Asked Questions (FAQ) - Student & Interview Review

Q1. What is the difference between Worker Threads and Child Processes in Node.js?

Child processes spawn completely separate operating system processes with their own memory space and full V8 instances, incurring high memory overhead (~30MB+ per process). Worker threads run in the SAME operating system process, sharing system resources and capable of sharing memory directly via SharedArrayBuffer, making them lightweight and fast to spawn.

Q2. When should you use Worker Threads?

Use Worker Threads for CPU-intensive tasks such as cryptographic proof-of-work, machine learning matrix multiplication, image/video compression, large JSON parsing (hundreds of megabytes), and complex mathematical simulations that would otherwise freeze the single main event loop.

Q3. What is SharedArrayBuffer and Atomics in Worker Threads?

SharedArrayBuffer allows the main thread and worker threads to share the exact same physical memory buffer without serialization copies. The 'Atomics' object provides atomic operations (add, sub, load, store, wait, notify) to prevent race conditions and thread synchronization bugs.

Explore Related Node.js Tutorials & Next Steps

Boost your server-side JavaScript proficiency by studying these closely connected core modules:

Child Processes

Compare worker threads with heavy OS processes.

Cluster Module & Multi-Core

Clustering for network I/O vs workers for CPU.

Performance Profiling

Measure thread pool efficiency.

Share Share on Facebook Share on Twitter Share on LinkedIn Pin on Pinterest Share on Stumbleupon Share on Tumblr Share on Reddit Share on Diggit

You may also like this!