num_workers > 0 会卡住或报 pickleerror 是因为子进程需 pickle 反序列化 dataset、collate_fn 等,若含不可序列化对象(如 videocapture、lambda、动态 import、全局变量)或 windows 下未加 if name == '__main__': 保护导致重复执行,则失败。

num_workers 设为 0 是最安全的起点,尤其在 Windows 或使用 fork 多进程启动方式时;设为大于 0 时必须确保数据集、collate_fn 和 transform 都是可序列化的,且不能依赖全局状态或未初始化的模块。
为什么 num_workers > 0 会卡住或报 PickleError?
PyTorch DataLoader 在子进程中通过 pickle 重建 dataset、sampler、collate_fn 和 transform。一旦其中任意一个对象包含不可序列化的内容(比如 OpenCV VideoCapture 实例、lambda 函数、带闭包的局部函数、未 import 的模块引用),子进程就无法反序列化,导致卡死或抛出 PickleError。
常见踩坑点:
-
dataset.__init__()中直接创建了cv2.VideoCapture或h5py.File对象 - 用
lambda x: x + 1当作transform,而 lambda 无法被 pickle - 在
__getitem__中动态 import 某个模块(import 在子进程中可能失败或路径不同) - 使用了依赖全局变量的函数(如
global_cfg.batch_size),但该变量在子进程中未定义
Windows 下 num_workers > 0 必须加 if __name__ == '__main__':
Windows 默认使用 spawn 启动子进程,不共享父进程的运行上下文。若主模块未加保护,子进程会重复执行整个脚本,导致反复初始化模型、重载数据、甚至递归启动新进程——最终卡死或 OOM。
正确写法示例:
if __name__ == '__main__':
train_loader = DataLoader(dataset, batch_size=32, num_workers=4)
for epoch in range(10):
for batch in train_loader:
# 训练逻辑
pass
注意:if __name__ == '__main__': 必须包裹所有涉及 DataLoader 初始化和迭代的代码,不只是 DataLoader 创建那一行。
num_workers 设置多少才合适?
没有固定最优值,需结合 CPU 核心数、I/O 延迟、batch_size 和数据预处理开销实测。经验性建议:
- 从
num_workers=0跑通逻辑,再逐步增加 - Linux/macOS 上,
num_workers通常设为min(8, os.cpu_count() - 1),避免抢占训练主进程资源 - SSD + 简单 transform(如 ToTensor)时,
num_workers=2~4往往已足够;若用了 heavy augment(如 Albumentations 中的 large-scale warp),可尝试num_workers=6~8 - 观察
train_loader迭代时 GPU 利用率是否持续 >90%:如果经常掉到 0%,说明数据加载成为瓶颈,可增num_workers;如果增加后 GPU 利用率没提升,反而 CPU 占用飙高或内存暴涨,说明已过载
替代方案:prefetching + persistent_workers
PyTorch 1.7+ 提供了更精细的控制选项,比单纯调 num_workers 更有效:
-
persistent_workers=True:子进程在 epoch 结束后不销毁,避免反复 fork 开销(尤其适合小数据集多 epoch) -
pin_memory=True(配合cuda()使用):提前把 batch 张量锁页,加速 Host→GPU 传输 - 组合使用:
DataLoader(..., num_workers=4, persistent_workers=True, pin_memory=True)
注意:persistent_workers=True 要求 num_workers > 0,且必须在 if __name__ == '__main__': 内使用,否则 spawn 模式下仍会出错。
真正麻烦的从来不是数字填几,而是 dataset 的构造方式是否经得起跨进程重建——哪怕只改一行 self.video = cv2.VideoCapture(...) 放到 __getitem__ 里,都可能让 num_workers=1 突然失效。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











