导入PySide后,Python子进程在numpy dot时崩溃

9 投票
3 回答
1300 浏览
提问于 2025-04-18 08:06

我在我的电脑上遇到了一个很奇怪的问题,当我使用 Python 的多进程池(multiprocessing Pool)并且导入了 numpy 和 PySide 时,程序会卡住。这是我见过的最复杂的错误之一:) 下面的代码:

import numpy as np
import PySide


def hang():
    import multiprocessing
    pool = multiprocessing.Pool(processes = 1)
    pool.map(f, [None])


def f(ignore):
    print('before dot..')
    np.dot(np.zeros((128, 1)), np.zeros((1, 32)))
    print('after dot.')


if __name__ == "__main__":
    hang()
    print('success!')

只打印出 'before dot..',但实际上应该打印出

before dot..
after dot.
success!

我不是 gdb 的专家,但看起来 gdb 显示进程在 'np.dot' 这一行退出(或崩溃):

[Inferior 1 (process 2884) exited normally]

我可以做一些神奇的修改来防止程序卡住:

  • 如果你把传入 'dot' 的数组的形状从 128 减少到 127
  • (!) 如果你把传入 'dot' 的数组的形状从 128 增加到 256
  • 如果你不使用多进程,而是直接运行函数 'f'
  • (!!!) 如果你注释掉 PySide 的导入,因为在代码中并没有用到它

任何帮助都非常感谢!

包的版本:

numpy=1.8.1 或 1.7.1,PySide=1.2.1 或 1.2.2

Python 版本:

Python 2.7.5(默认,2013年9月12日,21:33:34) [GCC 4.2.1 兼容 Apple LLVM 5.0 (clang-500.0.68)] 在 darwin 上

或者

Python 2.7.6(默认,2014年4月9日,11:48:52) [GCC 4.2.1 兼容 Apple LLVM 5.1 (clang-503.0.38)] 在 darwin 上

注意:在寻找信息的过程中,我稍微简化了原始代码和问题。但这里有一堆更新,以便为可能遇到这个错误的其他人保留历史(例如,我最开始是从 matplotlib 开始的,而不是 pyside)

更新:我把 pylab 的导入缩小到只导入带有 pyside 后端的 matplotlib,并更新了代码以运行。

更新:我正在修改帖子,只导入 PySide,而不是:

import matplotlib
matplotlib.use('qt4agg')
matplotlib.rcParams['backend.qt4']='PySide'
import matplotlib.pyplot

更新:初步统计显示这是一个仅限 Mac 的问题。3 个人在 Ubuntu 上运行正常,2 个人在 Mac 上卡住。

更新:在 dot 操作之前打印 print(os.getpid()) 给我一个我在 'top' 中看不到的进程 ID,这显然意味着它崩溃了,而多进程在等待一个死掉的进程。因此我无法将调试器附加到它上面。我相应地编辑了主要问题。

3 个回答

0

我遇到了完全一样的问题。当子进程使用numpy.dot时,出现了死锁。但是当我把矩阵的大小减小时,它就能正常运行。所以我没有在一个包含156000个浮点数的矩阵上做点积,而是进行了3次52000个浮点数的点积,然后把结果拼接在一起。我不太确定最大限制是什么,也不清楚这是否和子进程的数量、可用内存或其他因素有关。不过,如果能通过反复试验找出不会死锁的最大矩阵,那么下面的代码应该会有帮助。

def get_batch(X, update_iter, batchsize):
    curr_ptr = update_iter*batchsize
    if X.shape[0] - curr_ptr <= batchsize :
        X_i = X[curr_ptr:, :]
    else:
        X_i = X[curr_ptr:curr_ptr+batchsize, :]
    return X_i

def batch_dot(X, w, batchsize):
    y = np.zeros((1,))
    num_batches = X.shape[0]/batchsize
    if X.shape[0]%batchsize != 0:
        num_batches += 1
    for batch_iter in range(0, num_batches):
        X_batch = get_batch(X, batch_iter, batchsize)
        y_batch = X_batch.dot(w)
        y = np.hstack((y, y_batch))
    return y[1:]
0

我觉得这是多进程模块的问题。

你可以试试下面这个方法。

import numpy as np
import PySide


    def hang():
        import multiprocessing.dummy as multiprocessing
        pool = multiprocessing.Pool(processes = 1)
        pool.map(f, [None])


    def f(ignore):
        print('before dot..')
        np.dot(np.zeros((128, 1)), np.zeros((1, 32)))
        print('after dot.')


    if __name__ == "__main__":
        hang()
        print('success!')
8

这是一个关于一些BLAS库的普遍问题,这些库是numpy在执行dot操作时使用的。

Apple的Accelerate库和用GNU Openmp构建的OpenBlas在进行多进程处理时,父进程和子进程之间的使用是有问题的,它们会导致死锁。

这个问题无法通过numpy来解决,但有三种解决方法:

  • 使用netlib BLAS、ATLAS,或者基于pthread的git master版本的OpenBlas(2.8.0版本不适用)
  • 使用Python 3.4及其新的多进程spawnforkserver启动方法
  • 使用线程而不是多进程,numpy在大多数耗时的操作中会释放全局解释器锁(GIL),这样在普通的桌面电脑上你可以获得不错的线程速度提升

撰写回答