单线程用importlib导入时scipy报字典迭代大小变化错误如何解决
问题根因分析
- 你观测到的单线程只是Python层面的线程,PyTorch导入时会隐式启动大量C层面的原生工作线程(包括CUDA调度线程、OpenBLAS/MKL线性代数运算线程),这类线程不会出现在
threading.enumerate()的返回结果中,但同样会参与GIL抢占。 - AWS G4实例启动初期处于资源预热阶段,GPU驱动、容器资源调度、CUDA运行时的初始化工作尚未稳定,原生线程的GIL抢占时机波动极大,刚好在scipy 1.6.3执行
list(globals().items())迭代全局字典视图的间隙,持有GIL修改了全局字典的内容,就会触发字典迭代时被修改的RuntimeError。 - 运行时间越长复现概率越低的原因是:启动阶段的各类初始化工作完成后,原生后台线程的活跃度大幅下降,刚好命中scipy导入时机的概率自然降低。
- 网上提到的提前加
pairs = []的方案本质是歪打正着:提前在全局字典中新增了pairs键,减少了迭代过程中新增键的概率,并没有从根源解决问题。
可行解决方案
- 方案1:调整导入时机
不要在业务实例方法中做延迟导入,将torch、filterpy的导入逻辑放到模块顶部,或者在程序启动的初始化阶段提前触发一次导入,避开启动阶段的高波动调度窗口。 - 方案2:增加导入重试逻辑
对importlib.import_module的调用增加3~5次重试,捕获RuntimeError后间隔100ms左右重试即可,该问题属于偶发调度冲突,重试几乎可以100%规避。示例代码如下:import importlib import time def safe_import(module_name, retry=3, interval=0.1): for _ in range(retry): try: return importlib.import_module(module_name) except RuntimeError as e: if "dictionary changed size during iteration" not in str(e): raise time.sleep(interval) return importlib.import_module(module_name) # 调用时替换原import逻辑 torch = safe_import("torch") kalman = safe_import("filterpy.kalman") - 方案3:升级依赖版本
scipy 1.7.0及以上版本已经重构了_discrete_distns.py中的全局字典遍历逻辑,从根源修复了该问题,升级scipy到1.7+版本即可彻底解决。 - 方案4:减少原生后台线程数量
在导入torch前设置环境变量限制线性代数库的线程数,降低GIL抢占概率:import os os.environ["OMP_NUM_THREADS"] = "1" os.environ["MKL_NUM_THREADS"] = "1" import torch
内容的提问来源于stack exchange,提问作者Greg0ry
相关产品推荐
相关产品推荐

