You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 19:33:11