跳到主要内容

进程✅

nodejs 进程间如何通信?

答案

核心概念:

  • 首选 IPC 通道:使用 child_process.fork/cluster 自带的进程间通道,父子进程可通过 process.send/on('message') 互发结构化数据,并可传递句柄(如 net.Server/socket)。
  • 标准流通信:spawn/exec 通过 stdin/stdout 建立字节/文本流,约定 JSON 行协议等实现无状态通信。
  • 套接字通信:使用 net/dgram 建立 TCP/UDP 或 Unix Domain Socket,适合跨进程/跨主机与多语言互通。
  • 能力边界:SharedArrayBuffer 仅线程间可共享(worker_threads),进程间不共享;需要通过 IPC/Socket/文件等媒介。
  • 可靠性:定义消息协议(类型、序列号、超时与重试),避免竞态;大对象注意复制与背压。
  • 主进程通过返回的子进程实例监听子进程回传事件
  • 子进程通过主进程传入的环境变量识别自身
  • 子进程基于 process.send 发送事件, proccess.on('message') 监听主进程消息
// fork_ipc.js — 使用 child_process.fork 的内置 IPC 通道(process.send/on('message'))
// 运行:node fork_ipc.js

const { fork } = require('node:child_process')
// 利用环境变量识别子进程
if (process.env.CHILD_FORK === '1') {
  // 子进程通过 on 监听事件
  process.on('message', (msg) => {
    console.log('[child] recv:', msg)
    // senf 发送事件
    process.send && process.send({ reply: 'pong', ts: Date.now() })
  })
  // 给父进程一个握手
  process.send && process.send({ hello: 'from child', pid: process.pid })
  // 2 秒后退出,给足往返时间
  setTimeout(() => process.exit(0), 2000)
} else {
  // 父进程逻辑
  const child = fork(__filename, { env: { ...process.env, CHILD_FORK: '1' } })
  // 通过子进程返回实例监听子进程回传事件
  child.on('message', (msg) => console.log('[parent] recv:', msg))
  child.once('spawn', () => {
    child.send({ ping: 'ping', ts: Date.now() })
  })
  child.on('exit', (code) => console.log('[parent] child exit', code))
}

Open browser consoleTerminal

提示

process.send 仅在通过 fork/cluster 或 spawn({stdio: ['pipe','pipe','pipe','ipc']}) 时可用;spawn/exec 默认没有 IPC 通道,仅有标准流。

延伸阅读

worker、child_process、cluster 是什么,有什么区别?

答案
  • worker_threads:同进程多线程,线程间共享内存(SharedArrayBuffer/Atomics),消息通道 MessagePort,适合 CPU 密集与低开销并行。
  • child_process:独立进程(spawn/exec/fork),进程级隔离,IPC or stdio 通信,可运行非 Node 命令,适合隔离、调用系统工具。
  • cluster:基于 child_process.fork 的多进程模型,主进程统一监听端口并做句柄分发/负载均衡,便于横向扩容利用多核。
对比项worker_threadschild_processcluster
隔离线程级,共享内存进程级,完全隔离多进程,主从模型
通信MessagePort、共享内存IPC/stdio/SocketIPC(封装在 cluster)
开销低(创建/切换轻)高(创建/内存独立)中(管理便捷)
场景CPU 密集、数据共享外部命令、隔离容错HTTP 服务多核扩展

示例说明

// 将 CPU 密集任务交给 worker,避免阻塞事件循环(典型场景:哈希/压缩/图像处理等)
const { Worker, isMainThread, parentPort, workerData } = require('node:worker_threads')
const crypto = require('node:crypto')

if (!isMainThread) {
  const { rounds, salt, password } = workerData
  // 模拟重 CPU 任务:同步 PBKDF2(纯 CPU,演示阻塞型计算交给 worker 处理)
  const hash = crypto.pbkdf2Sync(password, salt, rounds, 32, 'sha256').toString('hex')
  parentPort.postMessage({ hash })
} else {
  console.time('pbkdf2@worker')
  const w = new Worker(__filename, {
    workerData: { rounds: 200_000, salt: 'salty', password: 'secret' }
  })
  w.once('message', ({ hash }) => {
    console.timeEnd('pbkdf2@worker')
    console.log('hash:', hash.slice(0, 16) + '...')
    w.terminate()
  })
  w.once('error', err => console.error('worker error:', err))
  w.once('exit', code => code && console.error('worker exit code:', code))

  // 主线程可继续处理其它任务(若用 pbkdf2Sync 在主线程会卡住)
  setImmediate(() => console.log('main tick: event loop is free'))
}

Open browser consoleTerminal

延伸阅读:

用过 cluster 讲一下?

答案

核心概念:

  • 定位:cluster 基于 child_process.fork 的主-工多进程模型,用多进程利用多核,主进程统一管理与调度。
  • 句柄分发:主进程集中持有 server 句柄并将连接分配给各 worker(默认轮询),单端口对外、内部多进程处理。
  • 通信与治理:主/工通过 IPC 消息通信,可传递句柄;关注 worker 的 online/listening/exit 事件实现自愈。
  • 适用与边界:I/O 密集型 HTTP 服务横向扩展;有状态会话需粘滞或外部会话;CPU 密集更适合 worker_threads。

示例说明:

提示

默认调度策略为轮询(SCHED_RR),也可通过环境变量 NODE_CLUSTER_SCHED_POLICY 控制。会话粘滞可用四层代理(如 Nginx IP hash)或应用层粘滞实现。

延伸阅读:

Node 创建子进程方式有几种,有哪些区别?

答案
  • spawn:基于流的进程创建,stdout/stderr 为流,适合长时间、长输出与管道场景;不经 shell,安全可控。
  • exec:经 shell 执行命令,缓冲整段输出后一次返回,简单但有 maxBuffer 与 shell 注入风险。
  • execFile:不经 shell 直接执行可执行文件,等同 spawn 的简化封装,适合短输出的“命令+参数”。
  • fork:仅用于 Node 模块,自动建立 IPC 通道(process.send/on),可传递句柄,适合父子进程结构化通信。
方式是否经 shell输出形式通信典型场景
spawn流式(边产边读)管道/自建协议长跑任务、实时输出、管道
exec缓冲(收齐返回)回调返回整块短命令、脚本一把梭
execFile缓冲(整块)回调返回整块直接执行二进制/Node
fork否(Node 专用)IPC 消息process.send/on多进程协作、句柄传递

示例说明

// fork:仅 Node 模块,自动建立 IPC 通道,可传结构化消息
const { fork } = require('node:child_process')

if (process.env.ROLE === 'child') {
  process.on('message', m => process.send({ from: 'child', got: m }))
  return
}

const child = fork(__filename, [], { env: { ROLE: 'child' } })
child.once('message', m => {
  console.log('fork ipc ->', m)
  child.disconnect()
})
child.send({ type: 'ping', ts: Date.now() })

Open browser consoleTerminal

提示

长输出与实时日志用 spawn(流式、低内存);需要结构化父子通信用 fork。使用 exec 时留意 maxBuffer 与 shell 注入风险;无需 shell 请优先 execFile/spawn。

延伸阅读:

child.killchild.send 有哪些区别?

答案

结论:child.send 用于在具备 IPC 通道的父子进程间传递结构化数据与句柄;child.kill 用于向任意进程发送操作系统信号以控制其生命周期,不能承载数据。

维度child.sendchild.kill备注
语义IPC 消息OS 信号本质不同:数据通道 vs 控制信号
前提需 IPC 通道(fork/cluster 或 spawn 含 'ipc')只要有 PID 即可spawn/exec 默认无 IPC
载荷对象/Buffer/句柄(server/socket)仅信号,无数据句柄传递只能用 send
影响不结束进程,仅通信可能触发退出(如 SIGTERM)SIGKILL/SIGSTOP 不可捕获
跨平台一致Windows 支持有限Win 常见 SIGTERM/SIGINT/SIGKILL
返回值布尔:写入队列是否成功布尔:信号是否发送send 为异步投递语义

延伸阅读

child_process.fork 与 POSIX 的 fork 有什么区别?

答案
  • POSIX fork:内核复制当前进程(写时复制 COW),父子共享打开的 FD,从 fork 返回继续执行;无自动加载新程序,常与 execve 组合。
  • child_process.fork:启动一个新的 Node 进程加载目标模块,自动建立 IPC 通道(process.send/on),非内存复制语义,跨平台可用。
  • 能力差异:POSIX fork 可继承父进程完整状态(地址空间/FD);Node fork 仅启动新解释器与模块,无法共享内存(除显式 IPC/Socket)。
  • 选型:应用层多进程协作与消息通信用 child_process.fork;系统级进程复制/热继承需用原生 fork/exec(Node 不直接暴露)。
维度POSIX forkchild_process.fork
执行语义复制当前进程+COW,原地返回启动新 Node 进程加载模块
IPC无自动 IPC内置 IPC(process.send/on)
FD/句柄继承父进程已打开 FD不继承,支持通过 send 传句柄
跨平台Unix 专有跨平台(Windows/Unix)
典型用法fork→exec 链多进程 Node 服务与治理
/**
 * node_fork_ipc.js
 *
 * 目的:演示 child_process.fork 的内置 IPC(process.send/on)能力
 */

const { fork } = require('child_process')

const MODE = process.env.DEMO_MODE

if (MODE === 'FORK_CHILD') {
  console.log('[fork-child] process.send?', typeof process.send === 'function')
  process.on('message', msg => {
    console.log('[fork-child] got from parent:', msg)
    if (typeof process.send === 'function') {
      process.send({ type: 'pong', via: 'ipc', echo: msg })
    }
  })
  setTimeout(() => process.exit(0), 300)
} else {
  // 父进程:仅演示 fork 的 IPC
  (async function main () {
    console.log('== child_process.fork: Node 专用 IPC ==')
    const forked = fork(__filename, [], {
      env: { ...process.env, DEMO_MODE: 'FORK_CHILD' }
    })

    forked.on('message', m => {
      console.log('[parent] from fork-child:', m)
    })
    forked.on('exit', code => {
      console.log('[parent] fork-child exit with code', code)
    })
    forked.send({ type: 'ping', from: 'parent', note: 'hello via IPC' })
  })()
}

Open browser consoleTerminal

延伸阅读: