Python程序顺序调用后执行multiprocessing调用时冻结问题求助
pyvips与multiprocessing混合调用导致程序冻结问题排查与解决
问题现象
使用pyvips处理图像的Python程序中,单独顺序调用函数或单独使用multiprocessing模块时均正常,但先顺序调用再立即执行多进程调用时,程序会冻结在多进程部分的第一个“step 3”之前。怀疑与垃圾回收机制有关,但无法确定。
环境信息
- Python 3.11.3
- pyvips 2.2.2(搭配vips-8.15.1)
- Ubuntu 22.04 Linux系统
- Spyder的IPython控制台
复现代码
import multiprocessing import numpy as np import pyvips def myfunction(useless): print("step 1") image = pyvips.Image.new_from_array(np.zeros((8, 8, 3)), interpretation="rgb") print("step 2") res = np.asarray(image) print("step 3") return res def main(nbpool): srcrange = range(2) if nbpool == 0: res = list() for srcid in srcrange: res.append(myfunction(srcid)) return res else: pool = multiprocessing.Pool(nbpool) return pool.map(myfunction, srcrange) if __name__ == "__main__": res1 = main(0) print("Now with multiprocessing") res2 = main(1)
问题根源
这个问题的核心是pyvips的全局状态在fork后的子进程中发生冲突。multiprocessing.Pool默认使用fork方式创建子进程,主进程在顺序调用时已经初始化了pyvips的全局资源(如内部线程池、上下文状态),子进程fork后会继承这些状态,但pyvips的C底层库并未设计为支持fork后的状态复用,导致执行np.asarray(image)(本质是触发pyvips图像数据导出)时出现死锁。
解决方案
方案1:清理pyvips全局状态并在子进程中重新初始化
在主进程顺序调用完成后主动关闭pyvips上下文,同时在子进程中重新初始化pyvips,确保每个子进程的pyvips状态独立:
import multiprocessing import numpy as np import pyvips def myfunction(useless): # 子进程启动后重新初始化pyvips pyvips.init() print("step 1") image = pyvips.Image.new_from_array(np.zeros((8, 8, 3)), interpretation="rgb") print("step 2") res = np.asarray(image) print("step 3") # 子进程结束前关闭pyvips上下文 pyvips.shutdown() return res def main(nbpool): srcrange = range(2) if nbpool == 0: res = list() for srcid in srcrange: res.append(myfunction(srcid)) # 顺序调用完成后清理主进程的pyvips状态 pyvips.shutdown() return res else: pool = multiprocessing.Pool(nbpool) return pool.map(myfunction, srcrange) if __name__ == "__main__": res1 = main(0) print("Now with multiprocessing") res2 = main(1)
方案2:使用spawn方式创建多进程
spawn方式会启动全新的Python解释器创建子进程,不会继承主进程的pyvips全局状态,从根源避免冲突。缺点是启动开销略高于fork,但适合复杂库场景:
import multiprocessing import numpy as np import pyvips def myfunction(useless): print("step 1") image = pyvips.Image.new_from_array(np.zeros((8, 8, 3)), interpretation="rgb") print("step 2") res = np.asarray(image) print("step 3") return res def main(nbpool): srcrange = range(2) if nbpool == 0: res = list() for srcid in srcrange: res.append(myfunction(srcid)) return res else: # 设置多进程启动方式为spawn multiprocessing.set_start_method('spawn') pool = multiprocessing.Pool(nbpool) return pool.map(myfunction, srcrange) if __name__ == "__main__": res1 = main(0) print("Now with multiprocessing") res2 = main(1)
内容的提问来源于stack exchange,提问作者proprit
相关产品推荐
相关产品推荐

