下面我来详细讲解这段代码以及"spawn"到底是什么。
1. 代码逐行解析
python
import multiprocessing
# 检查当前的进程启动方式是否为 "spawn"
if multiprocessing.get_start_method() != "spawn":
# 如果不是,则强制将其设置为 "spawn"
multiprocessing.set_start_method("spawn", force=True)
multiprocessing.get_start_method(): 获取当前系统默认创建新进程的方式。multiprocessing.set_start_method("spawn", force=True): 将创建新进程的方式设置为spawn。force=True表示即使之前已经设置过(或有默认值),也要强行覆盖。
2. 什么是 spawn?
在 Python 中,创建一个新的子进程主要有三种模式:fork、spawn 和 forkserver。
spawn 是其中一种最"干净"但启动较慢的模式。
形象的比喻:
- Fork (克隆) :这是 Linux/Unix 的默认模式。就像把父进程这个人瞬间"复印"了一份。
- 优点:速度极快,子进程继承了父进程的所有内存和资源。
- 缺点:如果父进程已经很乱了(比如已经开启了多线程,或者持有了某些锁),克隆出来的子进程可能会带着这些"坏毛病",导致死锁或程序崩溃。
- Spawn (新生) :这是 Windows 和 macOS 的默认模式。就像按照说明书重新雇佣了一个新员工。
- 优点 :非常安全和干净。子进程会启动一个新的 Python 解释器,只加载必要的资源。它不会继承父进程混乱的内存状态或锁。
- 缺点 :启动速度慢。因为要重新初始化 Python 环境和导入模块。
3. 为什么要用这段代码?
通常在以下几种情况下,你会看到这段代码:
-
跨平台兼容性:
- Linux 默认用
fork,Windows 默认用spawn。为了保证代码在 Linux 和 Windows 上行为一致,开发者会强制指定为spawn。
- Linux 默认用
-
深度学习(PyTorch/TensorFlow)与 CUDA:
- 这是最常见的原因。如果你在使用 PyTorch 进行 GPU 训练,CUDA 运行时不支持
fork。 - 如果在已经初始化了 CUDA 上下文的主进程中使用
fork创建子进程,程序会报错或卡死。因此,PyTorch 的DataLoader等组件通常要求使用spawn。
- 这是最常见的原因。如果你在使用 PyTorch 进行 GPU 训练,CUDA 运行时不支持
-
多线程与多进程混用:
- 如果你的主程序中已经启动了多个线程 ,然后再尝试
fork一个进程 ,非常容易导致死锁(因为fork会复制锁的状态,但不会复制持有锁的线程)。使用spawn可以避免这个问题。
- 如果你的主程序中已经启动了多个线程 ,然后再尝试
4. 使用 spawn 的注意事项
如果你强制使用了 spawn,你的代码必须遵守一个严格的规则:
所有的启动代码必须放在 if __name__ == '__main__': 保护块之下。
因为 spawn 会重新导入你的脚本文件来初始化子进程。如果你不加这个保护块,子进程在导入文件时,又会尝试启动新的孙子进程,导致无限递归循环报错。
错误示例:
python
import multiprocessing
multiprocessing.set_start_method("spawn", force=True)
def func():
print("hello")
# 这里会报错,因为子进程import时会再次执行这行
p = multiprocessing.Process(target=func)
p.start()
正确示例:
python
import multiprocessing
def func():
print("hello")
if __name__ == '__main__':
# 必须放在这里面
multiprocessing.set_start_method("spawn", force=True)
p = multiprocessing.Process(target=func)
p.start()
p.join()
总结
这段代码是为了确保多进程程序的稳定性和跨平台兼容性 ,特别是在涉及 GPU 计算或复杂多线程环境时,通过牺牲一点点启动速度(使用 spawn),换取更安全、干净的进程运行环境。