Unveiling Node.js: How Processes, Threads, and Clusters Really Work

This article explains Node.js's internal concurrency model, covering the distinction between processes and threads, debunking the single‑thread myth, and detailing how the event loop, child processes, cluster mode, and worker_threads enable high‑performance, multi‑core execution.

政采云技术
政采云技术
政采云技术
Unveiling Node.js: How Processes, Threads, and Clusters Really Work

Concepts

A process is an independent execution unit that the operating system schedules and allocates resources to, while a thread is a lightweight execution flow that lives inside a process and shares its resources.

Is Node.js Single‑Threaded?

Many developers claim that Node.js is single‑threaded, but a Node process actually contains multiple native threads.

# 示例一
require('http').createServer((req, res) => {
  res.writeHead(200);
  res.end('Hello World');
}).listen(8000);
console.log('process id', process.pid);

Running top -pid <pid> shows the #TH column with 7 threads, indicating that the process includes:

1 JavaScript main thread

1 watchdog thread for debugging information

1 V8 task‑scheduler thread

4 V8 worker threads for JIT compilation and GC

Libuv thread‑pool threads for asynchronous I/O

The size of the libuv I/O thread pool defaults to 4 when the program performs I/O; it can be changed with the process.env.UV_THREADPOOL_SIZE environment variable.

// V8 initialization thread pool
const int thread_pool_size = 4; // default 4 threads
default_platform = v8::platform::CreateDefaultPlatform(thread_pool_size);
V8::InitializePlatform(default_platform);
V8::Initialize();

Event Loop

Node uses libuv's event loop ( uv_event_loop) to achieve non‑blocking I/O. When the JavaScript thread encounters a blocking operation, the work is handed off to the libuv thread pool, and the result is later delivered to the main thread via a callback.

Child Processes

For CPU‑intensive tasks that would block the main thread, Node provides the child_process module to spawn separate processes.

spawn : creates a child process with streams for stdin/stdout/stderr.

exec : runs a command in a shell and buffers the output.

execFile : similar to exec but does not spawn a shell, offering slightly better performance.

fork : a specialized spawn for Node scripts that sets up an IPC channel.

Parent and child processes communicate via the IPC channel using process.send and the message event.

Example

# 主进程 (main_process.js)
const { fork } = require('child_process');
const child = fork('./fib.js');
child.send({ num: 44 });
child.on('message', message => {
  console.log('receive from child process, calculate result: ', message.data);
  child.kill();
});
child.on('exit', () => {
  console.log('child process exit');
});
setInterval(() => {
  console.log('continue execute javascript code', new Date().getSeconds());
}, 1000);
# 子进程 (fib.js)
function fib(num) {
  if (num === 0) return 0;
  if (num === 1) return 1;
  return fib(num - 2) + fib(num - 1);
}
process.on('message', msg => {
  const { num } = msg;
  const data = fib(num);
  process.send({ data });
});
process.on('SIGHUP', () => process.exit());
child pid 39974
continue execute javascript code 41
continue execute javascript code 42
continue execute javascript code 43
continue execute javascript code 44
receive from child process, calculate result: 1134903170
child process exit

Cluster Mode

The cluster module (available since Node v0.6) implements a master‑worker model for load‑balanced multi‑process servers. On Unix‑like systems it uses round‑robin scheduling; on Windows it uses a shared handle to pass sockets to workers.

# Cluster example (calculating Fibonacci numbers)
const cluster = require('cluster');
function fib(num) {
  if (num === 0) return 0;
  if (num === 1) return 1;
  return fib(num - 2) + fib(num - 1);
}
if (cluster.isMaster) {
  for (let i = 43; i < 45; i++) {
    const worker = cluster.fork();
    worker.send({ num: i });
    worker.on('message', message => {
      console.log(`receive fib(${message.num}) calculate result ${message.data}`);
      worker.kill();
    });
  }
  cluster.on('exit', worker => {
    console.log('worker ' + worker.process.pid + ' killed!');
    if (Object.keys(cluster.workers).length === 0) {
      console.log('calculate main process end');
    }
  });
} else {
  process.on('message', message => {
    const { num } = message;
    const data = fib(num);
    process.send({ data, num });
  });
}

Worker Threads

Since Node v10, the worker_threads module allows creating multiple threads inside a single process, suitable for CPU‑bound work without blocking the event loop.

Creation

const { Worker } = require('worker_threads');
const worker = new Worker('worker.js');

Communication via parentPort

const { Worker, isMainThread, parentPort } = require('worker_threads');
function fib(num) { /* same implementation as above */ }
if (isMainThread) {
  const worker = new Worker(__filename);
  worker.once('message', message => {
    console.log(`Fibonacci(${message.num}) is ${message.result}`);
    process.exit();
  });
  worker.postMessage(43);
} else {
  parentPort.once('message', num => {
    const result = fib(num);
    parentPort.postMessage({ num, result });
  });
}

MessageChannel for Direct Thread‑to‑Thread Communication

const { isMainThread, parentPort, MessageChannel, Worker } = require('worker_threads');
if (isMainThread) {
  const worker1 = new Worker(__filename);
  const worker2 = new Worker(__filename);
  const subChannel = new MessageChannel();
  worker1.postMessage({ port: subChannel.port1 }, [subChannel.port1]);
  worker2.postMessage({ port: subChannel.port2 }, [subChannel.port2]);
} else {
  parentPort.once('message', value => {
    value.port.postMessage(`Hi, I am thread${value.port}`);
    value.port.on('message', msg => {
      console.log(`thread${value.port} receive: ${msg}`);
    });
  });
}

Summary

Node.js runs JavaScript on a single main thread but relies on libuv's thread pool, child processes, the cluster module, and worker_threads to achieve high concurrency, handle I/O efficiently, and leverage multi‑core CPUs for CPU‑intensive workloads.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Node.jsThreadClusterprocessevent loopchild_processWorker Threads
政采云技术
Written by

政采云技术

ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.