操作系统进程与 Node.js 进程
本章解决的问题
- 进程和线程有什么区别?
- Node.js “单线程”到底指什么?
- 父子进程、Cluster Worker 为什么不能直接共享变量?
工作原理
进程是操作系统分配资源和隔离程序的一种单位,通常拥有 PID、虚拟地址空间、打开的文件描述符和权限。线程是进程内部的执行单位,同一进程中的线程可访问同一地址空间;不同进程默认不能直接访问彼此内存。
Node.js 执行普通 JavaScript 时,主要由一个 JavaScript 主线程运行 Event Loop。但一个 Node.js 进程还可能包含:
- V8 和 Node.js 内部线程;
- libuv 线程池,处理部分文件系统、DNS、加密等任务;
- 操作系统异步 I/O;
- 由应用显式创建的 Worker Threads。
因此,“JavaScript 默认在一个主线程执行”比“Node.js 只有一个线程”准确。
Node.js 进程 PID 100
├─ JavaScript 主线程:回调、Promise、Event Loop
├─ libuv 线程池:部分阻塞型底层工作
└─ 系统异步 I/O
另一个 Node.js 进程 PID 101
└─ 拥有独立 V8、Event Loop、堆和模块缓存
高频 API
console.log({
pid: process.pid,
parentPid: process.ppid,
platform: process.platform,
architecture: process.arch,
});
process.pid:当前进程 ID。process.ppid:创建当前进程的父进程 ID。process.platform:如win32、linux、darwin。process.arch:如x64、arm64。
具体代码:验证内存不共享
保存为 counter.ts:
let counter: number = 0;
counter += 1;
console.log({
pid: process.pid,
counter,
});
分别执行两次:
ts-node counter.ts
ts-node counter.ts
两个进程都会输出 counter: 1,而不是第二次输出 2。模块级变量只属于各自进程。
服务中也一样:若 4 个 Cluster Worker 各有一个 Map<string, Task>,实际是 4 张互不相同的表。共享任务状态应放入数据库、Redis 等外部系统。
正常时间线
Shell 创建 Node.js 进程
→ 操作系统分配 PID 和地址空间
→ Node.js 初始化 V8/libuv
→ 执行入口模块
→ Event Loop 无待处理工作
→ 进程退出并向父进程报告退出状态
异常时间线
入口模块抛出未捕获异常
→ Node.js 打印诊断到 stderr
→ 进程以非零状态退出
→ 该进程内存和连接全部消失
→ 其他隔离进程通常仍可运行
进程隔离可以限制一次崩溃的范围,但不会自动恢复任务、释放外部锁或保证数据一致性。
Windows 与 Linux
- 两者都有 PID/PPID,但 PID 会复用,不能永久代表某个任务。
- Linux 可用
ps -ef、pstree -p查看关系;PowerShell 可用Get-Process,父子关系可借助 CIM 查询。 - Linux 常见 fork/exec 模型;Node.js 的
child_process.fork()是“启动带 IPC 的新 Node.js 进程”,不要把它理解成直接暴露 POSIXfork(2)。 - Linux 的 POSIX 信号和进程组能力更完整;Windows 行为会在后文单独说明。
常见错误
- 把异步 I/O 当成多线程执行 JavaScript 回调:回调最终仍回到主线程排队执行。
- 用 Cluster 解决 CPU 密集型请求,却没有控制进程数和内存:每个进程都带独立 V8 堆。
- 认为
process.ppid永远有效:父进程可能先退出,关系也可能被系统重新托管。 - 用进程内变量实现多进程全局限流:每个 Worker 都会独立计数。
最佳实践
- I/O 密集型服务先写好异步流程,不要为“并发”盲目创建进程。
- CPU 密集型 JavaScript 优先评估
worker_threads;调用外部程序或需要故障隔离时用child_process。 - 多个 HTTP 进程共享端口与生命周期管理时再考虑
cluster或 PM2/容器编排。 - 共享状态显式放在共享存储中,并设计并发控制。
练习题
- 连续运行
counter.ts三次,记录 PID 与 PPID。 - 在脚本中创建一个大数组,观察每个独立进程是否各自占用内存。
- 判断以下对象能否直接传给另一个进程:函数、JSON 对象、数据库连接、文件路径。
验收点
- [ ] 能区分进程隔离与线程共享内存。
- [ ] 能解释 Node.js 单线程说法的边界。
- [ ] 知道
fork()、Cluster Worker 和 Worker Thread 不是同一机制。 - [ ] 不会用模块级变量保存多进程共享状态。