multiprocessing的spawn 模式

1、核心概念与背景

1.1 三种启动模式对比

Python 3.8+ 支持三种创建子进程的方式:

模式 平台 机制 速度 继承性 安全性
fork Unix/macOS 复制父进程内存空间(OS fork) ⚡ 极快 继承所有资源 ⚠️ 线程不安全
spawn Unix/macOS/Windows 从头启动全新的 Python 解释器 🐢 较慢 不继承资源 ✅ 安全
forkserver Unix 启动一个 server 进程,由其 fork ⚡ 快 部分继承 ✅ 较安全

1.2 为什么 Windows 默认用 spawn?

  • Windows 没有 fork() 系统调用
  • 若使用 fork,线程锁会被复制到新进程,可能引发死锁
  • spawn 虽然慢,但干净、安全、可预测

2、spawn 模式的底层原理

Python 的 spawn 模式是多进程启动中最"干净"但也最重的方案。其核心是子进程不复制父进程内存,而是启动一个全新的 Python 解释器**,再通过序列化传递执行目标。

2.1 核心原理

spawnfork 的本质区别:

特性 spawn fork
启动方式 新建 Python 解释器进程 系统调用 fork() 复制地址空间
内存继承 ❌ 不继承父进程内存 ✅ 继承(写时复制)
启动速度 慢(需重新初始化)
线程安全 ✅ 安全(无锁复制问题) ❌ 不安全(仅复制主线程)
全局变量 ❌ 子进程不可见 ✅ 子进程可见
参数传递 必须通过 pickle 序列化 直接共享内存

2.2 底层执行流程

#mermaid-svg-a8YHei1feukd6Xd4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a8YHei1feukd6Xd4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a8YHei1feukd6Xd4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a8YHei1feukd6Xd4 .error-icon{fill:#552222;}#mermaid-svg-a8YHei1feukd6Xd4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a8YHei1feukd6Xd4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a8YHei1feukd6Xd4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a8YHei1feukd6Xd4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a8YHei1feukd6Xd4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a8YHei1feukd6Xd4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a8YHei1feukd6Xd4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a8YHei1feukd6Xd4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a8YHei1feukd6Xd4 .marker.cross{stroke:#333333;}#mermaid-svg-a8YHei1feukd6Xd4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a8YHei1feukd6Xd4 p{margin:0;}#mermaid-svg-a8YHei1feukd6Xd4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-a8YHei1feukd6Xd4 .cluster-label text{fill:#333;}#mermaid-svg-a8YHei1feukd6Xd4 .cluster-label span{color:#333;}#mermaid-svg-a8YHei1feukd6Xd4 .cluster-label span p{background-color:transparent;}#mermaid-svg-a8YHei1feukd6Xd4 .label text,#mermaid-svg-a8YHei1feukd6Xd4 span{fill:#333;color:#333;}#mermaid-svg-a8YHei1feukd6Xd4 .node rect,#mermaid-svg-a8YHei1feukd6Xd4 .node circle,#mermaid-svg-a8YHei1feukd6Xd4 .node ellipse,#mermaid-svg-a8YHei1feukd6Xd4 .node polygon,#mermaid-svg-a8YHei1feukd6Xd4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a8YHei1feukd6Xd4 .rough-node .label text,#mermaid-svg-a8YHei1feukd6Xd4 .node .label text,#mermaid-svg-a8YHei1feukd6Xd4 .image-shape .label,#mermaid-svg-a8YHei1feukd6Xd4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-a8YHei1feukd6Xd4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-a8YHei1feukd6Xd4 .rough-node .label,#mermaid-svg-a8YHei1feukd6Xd4 .node .label,#mermaid-svg-a8YHei1feukd6Xd4 .image-shape .label,#mermaid-svg-a8YHei1feukd6Xd4 .icon-shape .label{text-align:center;}#mermaid-svg-a8YHei1feukd6Xd4 .node.clickable{cursor:pointer;}#mermaid-svg-a8YHei1feukd6Xd4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-a8YHei1feukd6Xd4 .arrowheadPath{fill:#333333;}#mermaid-svg-a8YHei1feukd6Xd4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-a8YHei1feukd6Xd4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-a8YHei1feukd6Xd4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a8YHei1feukd6Xd4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-a8YHei1feukd6Xd4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a8YHei1feukd6Xd4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-a8YHei1feukd6Xd4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-a8YHei1feukd6Xd4 .cluster text{fill:#333;}#mermaid-svg-a8YHei1feukd6Xd4 .cluster span{color:#333;}#mermaid-svg-a8YHei1feukd6Xd4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-a8YHei1feukd6Xd4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-a8YHei1feukd6Xd4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-a8YHei1feukd6Xd4 .icon-shape,#mermaid-svg-a8YHei1feukd6Xd4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a8YHei1feukd6Xd4 .icon-shape p,#mermaid-svg-a8YHei1feukd6Xd4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-a8YHei1feukd6Xd4 .icon-shape .label rect,#mermaid-svg-a8YHei1feukd6Xd4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a8YHei1feukd6Xd4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-a8YHei1feukd6Xd4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-a8YHei1feukd6Xd4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 父进程

import multiprocessing
创建 Process(target=func, args=(x,))
调用 p.start()
启动模式判断

start_method='spawn'
操作系统 exec* 启动

全新的 python 解释器
子进程: 重新导入 main 模块
子进程: 从 sys.argv 读取标记
子进程: 反序列化 target / args

(通过管道/共享内存接收 pickle 数据)
子进程: 执行 func(x)
子进程退出
父进程继续执行

等待 join() 或异步运行


2.3 内存与资源边界

#mermaid-svg-za06OvCa2RvA2e8Y{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-za06OvCa2RvA2e8Y .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-za06OvCa2RvA2e8Y .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-za06OvCa2RvA2e8Y .error-icon{fill:#552222;}#mermaid-svg-za06OvCa2RvA2e8Y .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-za06OvCa2RvA2e8Y .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-za06OvCa2RvA2e8Y .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-za06OvCa2RvA2e8Y .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-za06OvCa2RvA2e8Y .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-za06OvCa2RvA2e8Y .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-za06OvCa2RvA2e8Y .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-za06OvCa2RvA2e8Y .marker{fill:#333333;stroke:#333333;}#mermaid-svg-za06OvCa2RvA2e8Y .marker.cross{stroke:#333333;}#mermaid-svg-za06OvCa2RvA2e8Y svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-za06OvCa2RvA2e8Y p{margin:0;}#mermaid-svg-za06OvCa2RvA2e8Y .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-za06OvCa2RvA2e8Y .cluster-label text{fill:#333;}#mermaid-svg-za06OvCa2RvA2e8Y .cluster-label span{color:#333;}#mermaid-svg-za06OvCa2RvA2e8Y .cluster-label span p{background-color:transparent;}#mermaid-svg-za06OvCa2RvA2e8Y .label text,#mermaid-svg-za06OvCa2RvA2e8Y span{fill:#333;color:#333;}#mermaid-svg-za06OvCa2RvA2e8Y .node rect,#mermaid-svg-za06OvCa2RvA2e8Y .node circle,#mermaid-svg-za06OvCa2RvA2e8Y .node ellipse,#mermaid-svg-za06OvCa2RvA2e8Y .node polygon,#mermaid-svg-za06OvCa2RvA2e8Y .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-za06OvCa2RvA2e8Y .rough-node .label text,#mermaid-svg-za06OvCa2RvA2e8Y .node .label text,#mermaid-svg-za06OvCa2RvA2e8Y .image-shape .label,#mermaid-svg-za06OvCa2RvA2e8Y .icon-shape .label{text-anchor:middle;}#mermaid-svg-za06OvCa2RvA2e8Y .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-za06OvCa2RvA2e8Y .rough-node .label,#mermaid-svg-za06OvCa2RvA2e8Y .node .label,#mermaid-svg-za06OvCa2RvA2e8Y .image-shape .label,#mermaid-svg-za06OvCa2RvA2e8Y .icon-shape .label{text-align:center;}#mermaid-svg-za06OvCa2RvA2e8Y .node.clickable{cursor:pointer;}#mermaid-svg-za06OvCa2RvA2e8Y .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-za06OvCa2RvA2e8Y .arrowheadPath{fill:#333333;}#mermaid-svg-za06OvCa2RvA2e8Y .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-za06OvCa2RvA2e8Y .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-za06OvCa2RvA2e8Y .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-za06OvCa2RvA2e8Y .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-za06OvCa2RvA2e8Y .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-za06OvCa2RvA2e8Y .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-za06OvCa2RvA2e8Y .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-za06OvCa2RvA2e8Y .cluster text{fill:#333;}#mermaid-svg-za06OvCa2RvA2e8Y .cluster span{color:#333;}#mermaid-svg-za06OvCa2RvA2e8Y div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-za06OvCa2RvA2e8Y .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-za06OvCa2RvA2e8Y rect.text{fill:none;stroke-width:0;}#mermaid-svg-za06OvCa2RvA2e8Y .icon-shape,#mermaid-svg-za06OvCa2RvA2e8Y .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-za06OvCa2RvA2e8Y .icon-shape p,#mermaid-svg-za06OvCa2RvA2e8Y .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-za06OvCa2RvA2e8Y .icon-shape .label rect,#mermaid-svg-za06OvCa2RvA2e8Y .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-za06OvCa2RvA2e8Y .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-za06OvCa2RvA2e8Y .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-za06OvCa2RvA2e8Y :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 子进程地址空间(spawn 后)
exec 新解释器

不复制内存
管道/队列传输

pickle 序列化
父进程地址空间
全局变量 g_var=100
已导入模块 numpy, pandas
Thread-1 (后台线程)
Process 对象

target=worker, args=(data,)
main 重新执行
仅继承文件描述符

0,1,2 (stdin/stdout/stderr)
通过 unpickle 重建 args
data' = pickle.loads(data)
执行 worker(data')


2.4 关键代码层面的行为

当使用 spawn 时,子进程会重新加载启动模块,这意味着:

python 复制代码
import multiprocessing as mp

def worker(name):
    print(f"Hello {name}")

if __name__ == '__main__':
    mp.set_start_method('spawn')
    p = mp.Process(target=worker, args=('Alice',))
    p.start()   # 触发 spawn 流程
    p.join()

子进程实际执行的伪代码逻辑:

python 复制代码
# 子进程内部(C 层实现,Python 不可见)
import sys
sys.path = [父进程 sys.path...]      # 继承环境
# 重新导入 __main__,但通过隐藏标记跳过 if __name__=='__main__' 之后的代码
# 从管道读取 pickled (worker, ('Alice',))
func, args = unpickle_from_pipe()
func(*args)                          # 真正调用目标函数

2.5 为什么 spawn 更安全?

#mermaid-svg-9iJxlULE5ZOVLBvi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9iJxlULE5ZOVLBvi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9iJxlULE5ZOVLBvi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9iJxlULE5ZOVLBvi .error-icon{fill:#552222;}#mermaid-svg-9iJxlULE5ZOVLBvi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9iJxlULE5ZOVLBvi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9iJxlULE5ZOVLBvi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9iJxlULE5ZOVLBvi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9iJxlULE5ZOVLBvi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9iJxlULE5ZOVLBvi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9iJxlULE5ZOVLBvi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9iJxlULE5ZOVLBvi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9iJxlULE5ZOVLBvi .marker.cross{stroke:#333333;}#mermaid-svg-9iJxlULE5ZOVLBvi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9iJxlULE5ZOVLBvi p{margin:0;}#mermaid-svg-9iJxlULE5ZOVLBvi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9iJxlULE5ZOVLBvi .cluster-label text{fill:#333;}#mermaid-svg-9iJxlULE5ZOVLBvi .cluster-label span{color:#333;}#mermaid-svg-9iJxlULE5ZOVLBvi .cluster-label span p{background-color:transparent;}#mermaid-svg-9iJxlULE5ZOVLBvi .label text,#mermaid-svg-9iJxlULE5ZOVLBvi span{fill:#333;color:#333;}#mermaid-svg-9iJxlULE5ZOVLBvi .node rect,#mermaid-svg-9iJxlULE5ZOVLBvi .node circle,#mermaid-svg-9iJxlULE5ZOVLBvi .node ellipse,#mermaid-svg-9iJxlULE5ZOVLBvi .node polygon,#mermaid-svg-9iJxlULE5ZOVLBvi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9iJxlULE5ZOVLBvi .rough-node .label text,#mermaid-svg-9iJxlULE5ZOVLBvi .node .label text,#mermaid-svg-9iJxlULE5ZOVLBvi .image-shape .label,#mermaid-svg-9iJxlULE5ZOVLBvi .icon-shape .label{text-anchor:middle;}#mermaid-svg-9iJxlULE5ZOVLBvi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9iJxlULE5ZOVLBvi .rough-node .label,#mermaid-svg-9iJxlULE5ZOVLBvi .node .label,#mermaid-svg-9iJxlULE5ZOVLBvi .image-shape .label,#mermaid-svg-9iJxlULE5ZOVLBvi .icon-shape .label{text-align:center;}#mermaid-svg-9iJxlULE5ZOVLBvi .node.clickable{cursor:pointer;}#mermaid-svg-9iJxlULE5ZOVLBvi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9iJxlULE5ZOVLBvi .arrowheadPath{fill:#333333;}#mermaid-svg-9iJxlULE5ZOVLBvi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9iJxlULE5ZOVLBvi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9iJxlULE5ZOVLBvi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9iJxlULE5ZOVLBvi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9iJxlULE5ZOVLBvi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9iJxlULE5ZOVLBvi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9iJxlULE5ZOVLBvi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9iJxlULE5ZOVLBvi .cluster text{fill:#333;}#mermaid-svg-9iJxlULE5ZOVLBvi .cluster span{color:#333;}#mermaid-svg-9iJxlULE5ZOVLBvi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9iJxlULE5ZOVLBvi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9iJxlULE5ZOVLBvi rect.text{fill:none;stroke-width:0;}#mermaid-svg-9iJxlULE5ZOVLBvi .icon-shape,#mermaid-svg-9iJxlULE5ZOVLBvi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9iJxlULE5ZOVLBvi .icon-shape p,#mermaid-svg-9iJxlULE5ZOVLBvi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9iJxlULE5ZOVLBvi .icon-shape .label rect,#mermaid-svg-9iJxlULE5ZOVLBvi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9iJxlULE5ZOVLBvi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9iJxlULE5ZOVLBvi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9iJxlULE5ZOVLBvi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} spawn 的安全保证
父进程: 多线程 + 全局锁
启动全新解释器
子进程无父进程线程

无锁继承问题
安全执行
fork 的风险场景
父进程: 多线程 + 全局锁
fork() 仅复制主线程
子进程持有锁

但无解锁线程
死锁!

macOS 和 Windows 默认使用 spawn ,正是因为它们对 fork() 的支持有限或存在线程安全问题。


2.6 使用注意事项

  1. 必须加 if __name__ == '__main__':

    否则子进程重新导入模块时会无限递归创建新进程。

  2. 所有参数必须可 pickle

    spawn 通过 pickle 序列化 argskwargs,不可序列化的对象(如文件句柄、未绑定方法)会报错。

  3. 全局状态不会继承

    模块级全局变量、连接池等需要在子进程内重新初始化。

  4. 启动开销大

    频繁创建销毁进程时,优先使用进程池 Poolfork

2.7 了解spawn 模式下通过管道传递

在 spawn 模式下,父进程和子进程是完全独立的内存空间,所以参数需要通过操作系统提供的 IPC(进程间通信)机制传递。Python 的 multiprocessing 在 spawn 模式下使用的是 os.pipe() 或类似的管道机制,把 target 函数和 args 通过 pickle 序列化后写入管道,子进程启动后从管道读出并反序列化。

形象化比喻:

  1. 管道像一根水管
  2. pickle 序列化像把物品打包成标准的集装箱
  3. 子进程启动时像快递收件

我需要画一个生动的图和比喻。

想象 spawn 启动子进程,就像爸爸要派一个机器人去隔壁房间执行任务------但这个机器人出厂时是空白的,你必须把所有"指令"和"工具"从门缝底下塞过去。

形象比喻:门缝下的快递包裹
#mermaid-svg-njHplWD2lxdxoSuW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-njHplWD2lxdxoSuW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-njHplWD2lxdxoSuW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-njHplWD2lxdxoSuW .error-icon{fill:#552222;}#mermaid-svg-njHplWD2lxdxoSuW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-njHplWD2lxdxoSuW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-njHplWD2lxdxoSuW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-njHplWD2lxdxoSuW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-njHplWD2lxdxoSuW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-njHplWD2lxdxoSuW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-njHplWD2lxdxoSuW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-njHplWD2lxdxoSuW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-njHplWD2lxdxoSuW .marker.cross{stroke:#333333;}#mermaid-svg-njHplWD2lxdxoSuW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-njHplWD2lxdxoSuW p{margin:0;}#mermaid-svg-njHplWD2lxdxoSuW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-njHplWD2lxdxoSuW .cluster-label text{fill:#333;}#mermaid-svg-njHplWD2lxdxoSuW .cluster-label span{color:#333;}#mermaid-svg-njHplWD2lxdxoSuW .cluster-label span p{background-color:transparent;}#mermaid-svg-njHplWD2lxdxoSuW .label text,#mermaid-svg-njHplWD2lxdxoSuW span{fill:#333;color:#333;}#mermaid-svg-njHplWD2lxdxoSuW .node rect,#mermaid-svg-njHplWD2lxdxoSuW .node circle,#mermaid-svg-njHplWD2lxdxoSuW .node ellipse,#mermaid-svg-njHplWD2lxdxoSuW .node polygon,#mermaid-svg-njHplWD2lxdxoSuW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-njHplWD2lxdxoSuW .rough-node .label text,#mermaid-svg-njHplWD2lxdxoSuW .node .label text,#mermaid-svg-njHplWD2lxdxoSuW .image-shape .label,#mermaid-svg-njHplWD2lxdxoSuW .icon-shape .label{text-anchor:middle;}#mermaid-svg-njHplWD2lxdxoSuW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-njHplWD2lxdxoSuW .rough-node .label,#mermaid-svg-njHplWD2lxdxoSuW .node .label,#mermaid-svg-njHplWD2lxdxoSuW .image-shape .label,#mermaid-svg-njHplWD2lxdxoSuW .icon-shape .label{text-align:center;}#mermaid-svg-njHplWD2lxdxoSuW .node.clickable{cursor:pointer;}#mermaid-svg-njHplWD2lxdxoSuW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-njHplWD2lxdxoSuW .arrowheadPath{fill:#333333;}#mermaid-svg-njHplWD2lxdxoSuW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-njHplWD2lxdxoSuW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-njHplWD2lxdxoSuW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-njHplWD2lxdxoSuW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-njHplWD2lxdxoSuW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-njHplWD2lxdxoSuW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-njHplWD2lxdxoSuW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-njHplWD2lxdxoSuW .cluster text{fill:#333;}#mermaid-svg-njHplWD2lxdxoSuW .cluster span{color:#333;}#mermaid-svg-njHplWD2lxdxoSuW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-njHplWD2lxdxoSuW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-njHplWD2lxdxoSuW rect.text{fill:none;stroke-width:0;}#mermaid-svg-njHplWD2lxdxoSuW .icon-shape,#mermaid-svg-njHplWD2lxdxoSuW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-njHplWD2lxdxoSuW .icon-shape p,#mermaid-svg-njHplWD2lxdxoSuW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-njHplWD2lxdxoSuW .icon-shape .label rect,#mermaid-svg-njHplWD2lxdxoSuW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-njHplWD2lxdxoSuW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-njHplWD2lxdxoSuW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-njHplWD2lxdxoSuW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 子进程房间

全新的空房间
父进程房间
pickle.dumps()

打包成字节流
pickle.dumps()

打包成字节流
字节流通过管道传输
🤖 目标函数: worker
📦 参数: data=1,2,3
🚪 管道 Pipe

os.pipe()
空白 Python 解释器

刚刚被'生'出来
📬 从门缝接包裹
拆包裹 → 拿出函数和参数
开始执行 worker(data)

关键点 :两个房间(进程)完全不共享任何家具(内存)。唯一的连接是一根细管子 (Pipe),只能通过这根管子塞字节流,不能直接把 Python 对象"扔"过去。


2.8 为什么必须用 pickle "打包"?

因为管道传输的是原始字节bytes),不是 Python 对象:

你在 Python 里看到的 管道里实际传输的
def worker(name): ... b'\x80\x04\x95...' (二进制字节码)
data = {"key": [1, 2, 3]} b'\x80\x04\x95\x1c\x00...'

#mermaid-svg-d0n6rUKAKd6x25nx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-d0n6rUKAKd6x25nx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-d0n6rUKAKd6x25nx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-d0n6rUKAKd6x25nx .error-icon{fill:#552222;}#mermaid-svg-d0n6rUKAKd6x25nx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-d0n6rUKAKd6x25nx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-d0n6rUKAKd6x25nx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-d0n6rUKAKd6x25nx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-d0n6rUKAKd6x25nx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-d0n6rUKAKd6x25nx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-d0n6rUKAKd6x25nx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-d0n6rUKAKd6x25nx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-d0n6rUKAKd6x25nx .marker.cross{stroke:#333333;}#mermaid-svg-d0n6rUKAKd6x25nx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-d0n6rUKAKd6x25nx p{margin:0;}#mermaid-svg-d0n6rUKAKd6x25nx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-d0n6rUKAKd6x25nx .cluster-label text{fill:#333;}#mermaid-svg-d0n6rUKAKd6x25nx .cluster-label span{color:#333;}#mermaid-svg-d0n6rUKAKd6x25nx .cluster-label span p{background-color:transparent;}#mermaid-svg-d0n6rUKAKd6x25nx .label text,#mermaid-svg-d0n6rUKAKd6x25nx span{fill:#333;color:#333;}#mermaid-svg-d0n6rUKAKd6x25nx .node rect,#mermaid-svg-d0n6rUKAKd6x25nx .node circle,#mermaid-svg-d0n6rUKAKd6x25nx .node ellipse,#mermaid-svg-d0n6rUKAKd6x25nx .node polygon,#mermaid-svg-d0n6rUKAKd6x25nx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-d0n6rUKAKd6x25nx .rough-node .label text,#mermaid-svg-d0n6rUKAKd6x25nx .node .label text,#mermaid-svg-d0n6rUKAKd6x25nx .image-shape .label,#mermaid-svg-d0n6rUKAKd6x25nx .icon-shape .label{text-anchor:middle;}#mermaid-svg-d0n6rUKAKd6x25nx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-d0n6rUKAKd6x25nx .rough-node .label,#mermaid-svg-d0n6rUKAKd6x25nx .node .label,#mermaid-svg-d0n6rUKAKd6x25nx .image-shape .label,#mermaid-svg-d0n6rUKAKd6x25nx .icon-shape .label{text-align:center;}#mermaid-svg-d0n6rUKAKd6x25nx .node.clickable{cursor:pointer;}#mermaid-svg-d0n6rUKAKd6x25nx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-d0n6rUKAKd6x25nx .arrowheadPath{fill:#333333;}#mermaid-svg-d0n6rUKAKd6x25nx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-d0n6rUKAKd6x25nx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-d0n6rUKAKd6x25nx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d0n6rUKAKd6x25nx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-d0n6rUKAKd6x25nx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d0n6rUKAKd6x25nx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-d0n6rUKAKd6x25nx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-d0n6rUKAKd6x25nx .cluster text{fill:#333;}#mermaid-svg-d0n6rUKAKd6x25nx .cluster span{color:#333;}#mermaid-svg-d0n6rUKAKd6x25nx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-d0n6rUKAKd6x25nx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-d0n6rUKAKd6x25nx rect.text{fill:none;stroke-width:0;}#mermaid-svg-d0n6rUKAKd6x25nx .icon-shape,#mermaid-svg-d0n6rUKAKd6x25nx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d0n6rUKAKd6x25nx .icon-shape p,#mermaid-svg-d0n6rUKAKd6x25nx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-d0n6rUKAKd6x25nx .icon-shape .label rect,#mermaid-svg-d0n6rUKAKd6x25nx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d0n6rUKAKd6x25nx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-d0n6rUKAKd6x25nx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-d0n6rUKAKd6x25nx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} pickle.dumps

序列化
Python 对象

func + args
字节流 bytes

b'\\x80\\x04...'
写入管道写端

os.write(pipe_w, data)
管道读端

os.read(pipe_r)
pickle.loads

反序列化
子进程还原出

func' + args'

形象说pickle 就像复印机 + 碎纸机 + 传真机

  1. 父进程把函数和参数"复印"成二进制纸条
  2. 把纸条撕碎成字节流塞进管道
  3. 子进程从管道另一头取出字节,用同样的规则"拼回来"

2.9 代码层面的真相

当调用 p.start() 时,C 层代码实际在做:

python 复制代码
# 伪代码:发生在 Python 解释器 C 源码里
import os
import pickle

# 父进程创建管道
read_fd, write_fd = os.pipe()  # 想象成一根空心钢管,两头开口

# 父进程:把任务打包,塞进管子
task_package = pickle.dumps((target_function, args, kwargs))
os.write(write_fd, task_package)  # 往管子里倒字节

# 子进程启动后(全新的空 Python)
os.exec*("python")  # 新房间亮灯

# 子进程:从管子另一头取出
raw_bytes = os.read(read_fd, max_size)  # 接包裹
func, args, kwargs = pickle.loads(raw_bytes)  # 拆包裹

func(*args, **kwargs)  # 终于开始干活

2.10 一个反直觉的事实

#mermaid-svg-B9bgvl7SzgVEaLHy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-B9bgvl7SzgVEaLHy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-B9bgvl7SzgVEaLHy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-B9bgvl7SzgVEaLHy .error-icon{fill:#552222;}#mermaid-svg-B9bgvl7SzgVEaLHy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-B9bgvl7SzgVEaLHy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-B9bgvl7SzgVEaLHy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-B9bgvl7SzgVEaLHy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-B9bgvl7SzgVEaLHy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-B9bgvl7SzgVEaLHy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-B9bgvl7SzgVEaLHy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-B9bgvl7SzgVEaLHy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-B9bgvl7SzgVEaLHy .marker.cross{stroke:#333333;}#mermaid-svg-B9bgvl7SzgVEaLHy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-B9bgvl7SzgVEaLHy p{margin:0;}#mermaid-svg-B9bgvl7SzgVEaLHy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-B9bgvl7SzgVEaLHy .cluster-label text{fill:#333;}#mermaid-svg-B9bgvl7SzgVEaLHy .cluster-label span{color:#333;}#mermaid-svg-B9bgvl7SzgVEaLHy .cluster-label span p{background-color:transparent;}#mermaid-svg-B9bgvl7SzgVEaLHy .label text,#mermaid-svg-B9bgvl7SzgVEaLHy span{fill:#333;color:#333;}#mermaid-svg-B9bgvl7SzgVEaLHy .node rect,#mermaid-svg-B9bgvl7SzgVEaLHy .node circle,#mermaid-svg-B9bgvl7SzgVEaLHy .node ellipse,#mermaid-svg-B9bgvl7SzgVEaLHy .node polygon,#mermaid-svg-B9bgvl7SzgVEaLHy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-B9bgvl7SzgVEaLHy .rough-node .label text,#mermaid-svg-B9bgvl7SzgVEaLHy .node .label text,#mermaid-svg-B9bgvl7SzgVEaLHy .image-shape .label,#mermaid-svg-B9bgvl7SzgVEaLHy .icon-shape .label{text-anchor:middle;}#mermaid-svg-B9bgvl7SzgVEaLHy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-B9bgvl7SzgVEaLHy .rough-node .label,#mermaid-svg-B9bgvl7SzgVEaLHy .node .label,#mermaid-svg-B9bgvl7SzgVEaLHy .image-shape .label,#mermaid-svg-B9bgvl7SzgVEaLHy .icon-shape .label{text-align:center;}#mermaid-svg-B9bgvl7SzgVEaLHy .node.clickable{cursor:pointer;}#mermaid-svg-B9bgvl7SzgVEaLHy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-B9bgvl7SzgVEaLHy .arrowheadPath{fill:#333333;}#mermaid-svg-B9bgvl7SzgVEaLHy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-B9bgvl7SzgVEaLHy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-B9bgvl7SzgVEaLHy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-B9bgvl7SzgVEaLHy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-B9bgvl7SzgVEaLHy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-B9bgvl7SzgVEaLHy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-B9bgvl7SzgVEaLHy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-B9bgvl7SzgVEaLHy .cluster text{fill:#333;}#mermaid-svg-B9bgvl7SzgVEaLHy .cluster span{color:#333;}#mermaid-svg-B9bgvl7SzgVEaLHy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-B9bgvl7SzgVEaLHy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-B9bgvl7SzgVEaLHy rect.text{fill:none;stroke-width:0;}#mermaid-svg-B9bgvl7SzgVEaLHy .icon-shape,#mermaid-svg-B9bgvl7SzgVEaLHy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-B9bgvl7SzgVEaLHy .icon-shape p,#mermaid-svg-B9bgvl7SzgVEaLHy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-B9bgvl7SzgVEaLHy .icon-shape .label rect,#mermaid-svg-B9bgvl7SzgVEaLHy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-B9bgvl7SzgVEaLHy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-B9bgvl7SzgVEaLHy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-B9bgvl7SzgVEaLHy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} pickle
父进程里的 worker 函数

id=0x7f8b1234
管道传输
子进程里的 worker' 函数

id=0x7f8c5678
注意:这是两个不同的函数对象!

只是代码逻辑相同

子进程还原出来的函数,内存地址完全不同 ,只是"长得一样"。就像你给朋友传真了一份文件,他手里那份和你手里那份不是同一张纸,只是内容相同。


2.11 一句话总结

spawn 模式下,父子进程像两个互相看不见的封闭实验室 ,唯一的联络方式是一根细管子 。你要传递任何东西,都必须先 pickle 打成二进制包裹 ,塞过去让对方拆开。这保证了安全隔离,但也意味着不可 pickle 的东西(如打开的文件、数据库连接、lambda 函数)根本塞不进管子

2.11 关键机制序列化与反序列化

spawn 的核心是 pickle 序列化。当你调用:

python 复制代码
p = Process(target=my_func, args=(obj1, obj2))
p.start()

实际发生的是:

复制代码
主进程:  my_func + (obj1, obj2)  ──pickle──>  字节流
                                ↓ 通过管道传递
子进程:  字节流  ──unpickle──>  重建函数和对象  ──>  执行

这意味着:

  • 所有 argskwargs、实例属性都必须可被 pickle
  • 全局变量不会被自动继承(与 fork 完全不同)

2.12 关键知识点详解

2.12.1 if __name__ == '__main__' 为何必须?

在 spawn 模式下,子进程会重新导入模块。如果没有这个保护:

python 复制代码
# bad_example.py
from multiprocessing import Process

def worker():
    print("working")

# ⚠️ 子进程导入此模块时,下面代码会再次执行
p = Process(target=worker)
p.start()  # 子进程再创建子进程... 无限递归!

正确写法:

python 复制代码
# good_example.py
from multiprocessing import Process

def worker():
    print("working")

if __name__ == '__main__':
    p = Process(target=worker)
    p.start()
    p.join()

2.12.2 不可 pickle 对象的典型陷阱

python 复制代码
from multiprocessing import Process

class MyClass:
    def __init__(self):
        self.lock = threading.Lock()  # ❌ Lock 不可 pickle
        self.db_conn = sqlite3.connect(":memory:")  # ❌ 连接不可 pickle

    def run(self):
        pass

obj = MyClass()
p = Process(target=obj.run)
p.start()  # TypeError: cannot pickle '_thread.lock' object

2.12. 3解决方案:__getstate__ / __setstate__

python 复制代码
import threading
from multiprocessing import Process

class MyClass:
    def __init__(self):
        self.data = "important"
        self.lock = threading.Lock()  # 不可序列化
        self._init_lock()

    def _init_lock(self):
        self.lock = threading.Lock()

    def __getstate__(self):
        """序列化前调用:移除不可序列化属性"""
        state = self.__dict__.copy()
        del state['lock']  # 不序列化锁
        return state

    def __setstate__(self, state):
        """反序列化后调用:重建资源"""
        self.__dict__.update(state)
        self._init_lock()  # 重新创建锁

    def run(self):
        with self.lock:
            print(f"Safe: {self.data}")

if __name__ == '__main__':
    obj = MyClass()
    p = Process(target=obj.run)
    p.start()
    p.join()

3、完整实战示例

3.1 基础示例:传递数据

python 复制代码
import os
from multiprocessing import Process, set_start_method

def worker(name, data_list):
    print(f"[子进程 {os.getpid()}] 收到: name={name}, list={data_list}")
    data_list.append(999)  # ⚠️ 修改不会影响父进程(spawn不共享内存)
    print(f"[子进程] 修改后: {data_list}")

if __name__ == '__main__':
    set_start_method('spawn', force=True)  # 显式设置
    
    my_data = [1, 2, 3]
    p = Process(target=worker, args=("test", my_data))
    p.start()
    p.join()
    
    print(f"[父进程 {os.getpid()}] 最终: {my_data}")  # [1, 2, 3]

3.2 使用 Pool 与 spawn

python 复制代码
from multiprocessing import Pool, set_start_method
import os

def init_worker():
    """每个子进程启动时调用一次"""
    print(f"Worker initialized in PID {os.getpid()}")

def square(x):
    return x * x

if __name__ == '__main__':
    set_start_method('spawn', force=True)
    
    # initializer 在 spawn 模式下非常有用,用于重建资源
    with Pool(processes=4, initializer=init_worker) as pool:
        results = pool.map(square, range(10))
        print(results)

3.3 Manager + spawn 的正确用法

python 复制代码
from multiprocessing import Process, Manager, set_start_method

def worker(shared_dict, shared_list, q):
    # 使用 Manager 创建的共享对象
    shared_dict['pid'] = os.getpid()
    shared_list.append('from_child')
    q.put("message")

if __name__ == '__main__':
    set_start_method('spawn', force=True)
    
    manager = Manager()
    d = manager.dict()
    l = manager.list()
    q = manager.Queue()
    
    # ✅ 传递的是代理对象,可以被 pickle
    p = Process(target=worker, args=(d, l, q))
    p.start()
    p.join()
    
    print(f"dict: {dict(d)}")
    print(f"list: {list(l)}")
    print(f"queue: {q.get()}")

4、性能与最佳实践

场景 建议
Windows 开发 默认 spawn,无需设置,但要确保代码兼容
跨平台兼容 始终使用 if __name__ == '__main__'
大对象传递 避免频繁传递大对象,使用 Manager 或文件/内存映射
资源初始化 initializer 参数在每个子进程中初始化一次
调试 spawn 问题 设置 multiprocessing.log_to_stderr() 查看详细日志

4.1 性能对比代码

python 复制代码
import time
from multiprocessing import Process, set_start_method

def noop():
    pass

# spawn 模式
set_start_method('spawn', force=True)
start = time.perf_counter()
for _ in range(10):
    p = Process(target=noop)
    p.start()
    p.join()
print(f"spawn 10次: {time.perf_counter() - start:.2f}s")
# 通常 2-5 秒

5、常见错误速查表

错误信息 原因 解决
TypeError: cannot pickle 'xxx' object 参数/实例含不可序列化对象 实现 __getstate__/__setstate__
RuntimeError: An attempt has been made to start a new process... 缺少 if __name__ == '__main__' 加上保护
AttributeError: Can't get attribute 'xxx' 子进程找不到函数定义 确保函数在顶级命名空间
EOFError / BrokenPipeError Manager 被提前关闭 确保 Manager 生命周期覆盖子进程

总结

spawn 模式的核心本质

子进程是一个全新的、干净的 Python 进程 ,它通过 pickle 序列化 接收任务参数,然后从头重新初始化运行环境。

这个设计虽然牺牲了 fork 的速度优势,但换来了:

  1. 线程安全性(无锁继承问题)
  2. 跨平台一致性(Windows 可用)
  3. 可预测性(无全局状态污染)

如果你的代码需要在 Windows 上运行,或者涉及多线程混合编程,spawn 是唯一可靠的选择