访问 cython 扩展类型成员时 nogil 是安全的

is nogil safe when accessing cython extension type members

来自

You should be able to access [an extension type's] cdef members [inside a nogil block]... and call their cdef functions that are marked as nogil.

然而,cython documentation不同意:

After the GIL is released, any operation that involves python objects must first reacquire the GIL.

我假设“python 对象”包括 cython 扩展类型。这让我认为下面的伪代码是不安全的,因为它包含了一个竞争条件,这是由于在没有 GIL 的情况下修改 python 对象而引起的:

def function(ExtensionType arg):
    with nogil:
        # long running task
        # modify arg's member

arg = ExtensionType()
function(arg)
# access arg's member

# (alternatively, accessing and modifying the member could be swapped, with the same issue)

我已将其扩展到实际代码中以说明我的困惑:

cimport cytime

cdef class Tester:
    cdef int val

    def get_val(self):
        return self.val

    def set_val(self):
        print("1: GIL acquired by set_val")
        with nogil:
            cytime.sleep(0.1) # give me the GIL later, I'm not ready for it yet
            self.val = 1
        print("3: GIL reacquired by set_val")

t = Tester()
t.set_val()
print("2: val should be 0, but actually is: " + str(t.get_val()))

我预计程序的执行会遵循:1、2、3。然而,这是输出:

1: GIL acquired by set_val
3: GIL reacquired by set_val
2: val should be 0, but actually is: 1

谁能解释一下?谢谢。

作为一个猜测,cdef class 个实例被重新计算,因此需要 GIL 保护。

Accessing/writing 到 self.val 没有 GIL 就可以了。你不需要为 self 做任何引用计数(因为你已经有了对它的引用,你不需要另一个)并且你不需要为 [= 做任何引用计数12=] 因为它是 C int。实际上,您可以在没有 GIL 的情况下对 cdef class 个实例执行合理的操作(例如访问 nogil cdef 方法)。

Cython 通常会阻止您在 nogil 块中执行需要 GIL 的操作(它并不完美,但通常相当彻底)。

请注意,如果您选择从多个线程访问 .val,那么您很可能会遇到竞争条件,这完全是您自己的错。我所说的“安全”是指 Python 引用计数状态不会被破坏。


您似乎对 nogil 块的作用有很大的误解,并且将其视为类似于协程的东西!?

当你释放 GIL 时发生的所有事情是它允许让 其他 Python 线程你有 运行。正在处理 set_val 的线程按逻辑顺序继续:它停止并等待一段时间,它设置值,它等待重新获取 GIL,它打印语句 3,它 returns 到全局范围,它打印 statement-2.