多线程prange循环引发“双重释放或损坏(快速停止)”错误

时间:2019-04-12 17:43:59

标签: python c numpy openmp cython

我对原始问题做了一些更改。事实证明, malloc 部分实际上可能是问题所在,如评论中所建议。

我想在Cython prange循环中运行一个函数,如下面的代码所示。此代码引发“ 双重释放或损坏(快速停止)”错误。

当我使用prange标志“ num_threads = 1”运行代码时,一切都很好。 我了解我的代码可能不是线程安全的,但我不明白为什么。

import numpy as np
cimport numpy as np
cimport cython
from cython.parallel import prange
from libc.stdlib cimport malloc, free

cdef int my_func(int[:] arr_cy, int c) nogil except -1:

    cdef int i
    cdef int *arr_to_process = <int *>malloc(c * sizeof(int))
    if not arr_to_process:
        with gil:
            raise MemoryError()
    try:
        for i in range(c):
            arr_to_process[i] = 1
    finally:
        free(arr_to_process)
    return 0

def going(a):
    cdef int c 
    for c in prange(100000, nogil=True, num_threads=2):
        my_func(a, c)

def get_going(iterations):
    arr = np.arange(1000000, dtype=np.intc)
    cdef int [:] arr_v = arr

    for a in range(iterations):
        print('iter %i' %a)
        going(arr_v)

如果我以足够的迭代次数(例如30)运行get_going(iterations),则始终会引发错误。我觉得我很笨,但我不明白。谢谢您的帮助。

2 个答案:

答案 0 :(得分:1)

我最初确定了一个不会引起您问题的问题,但确实需要修复(现在已在编辑的代码中修复):Cython无法知道已引发异常-在C API中,异常是通过返回NULL来表示,但您的函数是void。参见the relevant bit of the documentation。您有两个选择:用except *定义函数以始终检查异常,或用错误代码定义它:

    cdef int my_func(int[:] arr_cy, int c) nogil except 1:
        # ... code goes here
        return 0 # indicate no error

Cython will automatically use this when you raise an exception.

实际问题是第my_func(a, c)行。从Numpy数组到memoryview的转换确实需要某种锁定(即GIL),或者存在带有引用计数的竞争条件。这种竞争状态导致它在不应该释放时被释放,因此出现错误

解决方案是在循环外生成a的内存视图:

cdef int[:] a_mview = a
# then inside the prange loop
     my_func(a_mview, c).

在并行部分中可以使用memoryview,但这只是最初的创建而已。我认为Cython在编译时未将其标记为错误是一个错误,可能值得reporting

答案 1 :(得分:0)

@DavidW的回答是可以的,但是它不能完全解决问题。经过一番摆弄之后,我找到了我要寻找的东西:我需要对内存视图使用指针,如cython docs上的通过指针从C函数传递数据部分所述。这是完整的工作代码。

cdef int my_func(int arr_cy[], int c) nogil except -1:

    cdef int i
    cdef int *arr_to_process = <int *>malloc(c * sizeof(int))
    if not arr_to_process:
        with gil:
            raise MemoryError()
    try:
        for i in range(c):
            arr_to_process[i] = 1
    finally:
        free(arr_to_process)
    return 0

def going(a):
    cdef int c
    cdef int [:1] arr_v = a
    for c in prange(100000, nogil=True, num_threads=2):
        my_func(&arr_v[0], c)

def get_going(it):
    arr = np.arange(1000000, dtype=np.intc)

    for ii in range(it):
        print('iter %i' %ii)
        going(arr)