先记住这个答案
child_process.fork 是启动新的 Node.js 进程并建立 IPC 通道的专用接口,父子进程可用 send 和 message 交换消息;spawn 更通用,可以启动其他可执行程序。Node 的 fork 不会像某些操作系统 fork 用法那样直接复制当前 JavaScript 执行状态,父子进程有各自的运行时和内存。消息经过序列化,发送成功也不等于任务已完成,因此工程协议通常需要请求标识、结果确认、错误响应、流量控制和断连处理。
- fork 便于启动 Node 模块并建立 IPC
- 父子进程拥有独立内存与运行时
- 发送完成与业务完成必须分开确认
独立进程意味着数据需要明确传递
父级修改一个对象,不会自动改变子级已经收到的对象。默认消息序列化有支持范围,函数、闭包和任意实例不能当作共享执行对象传过去;选择高级序列化也不等于获得跨进程共享引用。
子模块需要通过入口参数或消息知道要做什么。若希望执行一项计算,应发送经过校验的数据与任务类型,而不是假设父进程局部变量或已打开的资源会天然出现在新进程里。
send 的返回与回调不是业务确认
send 返回 false 可能表示通道关闭或积压过大,调用方需要停止无界投递并处理回调错误。发送回调说明发送阶段的结果,不代表对方已经完成计算,更不代表结果已可靠写入业务存储。
可设计包含 requestId、操作类型和参数的请求,再让子进程返回同一 requestId 对应的成功或失败响应。这样可以关联乱序完成的任务,并在超时、重试和重复响应时明确是否应该再次执行或结算。
断连与退出要进入任务状态机
IPC 断开后,尚未收到业务确认的任务不能自动算成功。父级应根据协议标记未知或失败,决定是否重试;如果任务具有外部副作用,重试需要业务幂等标识,不能只靠重新创建进程避免重复执行。
进程数量和消息量都应有上限。为每个请求无限 fork 会增加内存与启动成本,子进程池则需要排队、健康检查和回收策略;这些是任务运行设计,不会由 IPC 通道自动提供。
容易答错的地方
- 把 fork 当成共享父级变量的快捷方式
- 新的 Node 进程拥有自己的执行环境,父级对象需要通过协议传递。即使传输支持更多数据类型,也要考虑复制、序列化与版本兼容,不能把两个进程中的对象身份当作同一个内存地址。
- 收到 send 回调就释放业务任务
- 发送成功与执行完成是两个阶段。如果子进程在收到消息后崩溃,业务结果可能还不存在;应等待明确结果或持久化确认,再释放相关任务状态,并处理确认丢失后的重试语义。
面试官还会怎么问?
spawn 能否也建立 IPC 通道?
可以通过适当 stdio 配置建立 IPC,但对 Node 子进程而言 fork 已封装常见入口与通信方式。选择时看目标程序和协议需求,不应把 fork 说成底层上完全不同、spawn 绝不可能具备的能力。
IPC 可以直接传函数给子进程执行吗?
普通消息序列化不会把函数闭包变成可在另一个进程继续执行的对象。应发送数据与受控操作标识,由子模块调用已部署的实现;执行任意代码是另一种任务模型,需要明确安全与隔离边界。
为什么需要 requestId 而不能只按返回顺序配对?
子进程可以并发处理多个异步任务,完成顺序未必等于接收顺序。显式标识能关联结果、识别迟到响应和重复通知,也便于在进程重启后区分旧任务与新任务,避免把结果交给错误请求。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。