num_workers=0时DataLoader“卡住”是预期行为而非bug,因__getitem__和collate_fn全在主线程同步执行,遇I/O或计算密集操作(如未缓存HDF5读取、cv2.imread大图)即阻塞训练,导致GPU空转。

num_workers=0 时 DataLoader 看起来“卡住”是预期行为
不是 bug,而是同步阻塞:设为 num_workers=0 后,__getitem__ 和 collate_fn 全部在主线程执行。一旦你的 __getitem__ 里有 I/O 或计算密集操作(比如未缓存的 h5py.File 读取、cv2.imread 加载大图、PIL.Image.open 解码),主线程就会停在那里,GPU 显存空着,nvidia-smi 显示 vutil 长期为 0%。
常见触发场景:
- Windows 上用
pathlib.Path.exists()检查路径——该操作在多线程/多进程下可能隐式加锁 - Jupyter Notebook 中直接运行 DataLoader 创建代码(未包裹
if __name__ == "__main__":) -
__getitem__内调用了torch.cuda.current_device()—— 子进程不继承 CUDA 上下文,但num_workers=0时它又能在主线程跑通,掩盖问题
num_workers>0 时 worker 进程卡死在 init 阶段
现象是训练卡在第一个 batch,top 看到多个 python 进程 CPU 占用低但不退出,timeout 参数完全不生效——因为超时只监控 get_next(),不监控子进程初始化。
根本原因是子进程启动时执行了阻塞逻辑,典型包括:
立即学习“Python免费学习笔记(深入)”;
- matplotlib 默认 GUI 后端(如
TkAgg)尝试开窗口线程 - 全局变量里存了不可 pickle 的对象(lambda、数据库连接、带闭包的函数)
-
Dataset.__init__中调用了torch.hub.load或加载大模型,触发隐式下载/初始化 - OpenCV 默认启用多线程:
cv2.setNumThreads(0)必须在每个 worker 的__getitem__开头或__init__里显式调用
验证方式:临时把 num_workers 改成 0,如果训练能跑通,就基本锁定是多进程环境问题。
Linux/macOS 下 num_workers 设太高反而拖慢训练
不是越多越快。当 num_workers 超过物理核心数 ×1.5,或远高于数据预处理瓶颈(通常是磁盘 I/O 或图像解码),就会出现:
- 上下文切换开销激增:
perf stat -e context-switches可观测到数量暴增 - 内存带宽饱和:
sar -r 1显示%memused > 95% - fork 启动方式导致每个 worker 复制主进程内存页(即使没改),引发隐式内存拷贝爆炸
- 共享存储竞争:NFS 文件系统上多 worker 并发
open()同一文件会触发分布式锁
实操建议:
- SSD + 小图(CIFAR):从
num_workers=2起步 - HDD + 大图(医学影像):可试
num_workers=8–12,但必须配合iostat -x 1监控%util是否持续 100% - 务必配
pin_memory=True(仅 GPU 训练时),否则数据传回主进程会多一次内存拷贝
/dev/shm 空间不足导致 Bus error 或 Segmentation fault
PyTorch 默认用 /dev/shm(tmpfs)存放 worker 产出的 batch,Docker 默认只有 64MB。当 batch_size 大、图像分辨率高、num_workers 多时,极易撑爆,报错:
RuntimeError: DataLoader worker (pid XXX) is killed by signal: Bus errorRuntimeError: DataLoader worker (pid XXX) is killed by signal: Segmentation fault
解决方式分三类:
- Docker 启动时加
--shm-size="2G" - Linux 主机临时扩容:
sudo mount -t tmpfs -o size=2G tmpfs /dev/shm - 更稳妥绕过:设
pin_memory=False+prefetch_factor=2,让 worker 直接返回 Python 对象,不走共享内存(代价是 GPU 数据传输稍慢)
真正容易被忽略的是:这个限制和你代码里有没有 torch.compile、accelerate 无关,只取决于 batch 的实际内存占用和 /dev/shm 当前容量。每次换数据集或改分辨率,都得重新评估。


















