多线程 prange 循环抛出“double free or corruption (fasttop)”错误

Multithread prange loop throws "double free or corruption (fasttop)" error

提问人:ffi23 提问时间:4/13/2019 最后编辑:ffi23 更新时间:4/24/2019 访问量:653

问:

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

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

当我使用 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)

蟒蛇 c numpy openmp cython

评论

1赞 DavidW 4/13/2019
你可以通过改变各种东西来意外地避免这种与内存相关的错误——它们通常取决于逻辑错误(仍然存在)和内存中事物的任意排列(可能会随着你进行其他更改而改变)。这就是为什么这种问题需要一个最小的可重复的例子——没有它,我们推测是没有意义的
0赞 ffi23 4/13/2019
好的,谢谢@DavidW,我将发布一个完整的示例。然而,这意味着上面发布的代码没有任何问题,对吧?我对 Cython 很陌生,所以我不完全确定。
0赞 DavidW 4/13/2019
上面的代码没有明显错误。这种错误是由两次释放相同的内存引起的,因此 / 部分可能很重要。mallocfree

答:

1赞 DavidW 4/14/2019 #1

我最初发现了一个不会导致您的问题但确实需要修复的问题(现在已在编辑后的代码中修复):Cython 无法知道是否引发了异常 - 在 C API 中,异常通过返回 来指示,但您的函数是 。请参阅文档的相关部分。您有两种选择:定义函数 to always check for an exception,或使用错误代码定义它:NULLvoidexcept *

    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.

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

解决方案是在循环外部生成一个 memoryview:a

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

在并行部分中使用 memoryview 是可以的,但这只是初始创建的问题。我认为 Cython 在编译时没有将其标记为错误是一个错误,可能值得报告

评论

0赞 ffi23 4/14/2019
不幸的是没有解决问题。我已经根据您的建议更新了问题(尽管我不确定我是否理解为什么在将 c 传递给 prange 之前需要将 c 初始化为一个值?另请注意,我已将问题更新为 num_threads=2,因为它之前设置为 =1。虽然我确实在问题中解释说我只得到 num_threads=2 的错误,但也许不是很清楚。
0赞 DavidW 4/14/2019
对不起 - 我真的很愚蠢 - 我完全错过了它是循环变量。您无需初始化它。我的第二点仍然有效(但可能不是你的问题)。我稍后再看一眼c
0赞 DavidW 4/14/2019
@ffi23 查看我的编辑。我可以重现您的问题,我很确定这可以解决它num_threads=2
0赞 ffi23 4/16/2019
谢谢,但我确实在 prange 之前生成了一个 memoryview:.然后我只是将该 memoryview 对象传递给 my_func。然后我想我在这里创建了一个 memoryview 的 memoryview:那是因为我不知道该如何写那行。我应该传递指向数组的指针吗?我该怎么做?cdef int [:] arr_v = arrcdef int my_func(int[:] arr_cy, int c) nogil except -1:
0赞 DavidW 4/16/2019
但不知道这是Cython的记忆视图。因此,它必须创建该 memoryview 的 memoryview。您还可以通过定义来修复该问题,使其仅接受 memoryviews 作为参数goingdef going(int [:] a):
0赞 ffi23 4/24/2019 #2

@DavidW的答案是可以的,但并不是问题的完整答案。经过一番摆弄,我找到了我想要的东西:我需要为内存视图使用指针,如cython文档上的通过指针从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)