在两个 python 进程或 python 进程的不同内存中安全地写入 cython c 包装器中的变量



我正在创建一个 c 库的包装器,用于接收一些财务数据,我想将其收集到 python 数据类型中(带有字段名称列表的字典和带有财务数据字段的列表列表(。

在c级别,有一个函数开始"侦听"某个端口,当任何事件出现时,调用一些用户定义的函数。这个函数是用cython编写的。此类函数的简化示例如下:

cdef void default_listener(const event_data_t* data, int data_count, void* user_data):
cdef trade_t* trades = <trade_t*>data # cast recieved data according to expected type 
cdef dict py_data = <dict>user_data # cast user_data to initial type(dict in our case)
for i in range(data_count):
# append to list in the dict that we passed to the function 
# fields of recieved struct
py_data['data'].append([trades[i].price,
trades[i].size,
]
)

问题:当只有一个 python 进程启动此函数时,没有问题,但是如果我启动另一个 python 进程并运行相同的函数,其中一个进程将在不确定的时间内终止。我想发生这种情况是因为在不同进程中同时调用的两个函数可能会尝试写入内存的同一部分。可能是这样吗?

如果是这种情况,有没有办法防止两个进程使用相同的内存?或者也许可以在密码开始写入之前建立一些锁?

PS:我也读过这篇文章,根据它,对于每个python进程,分配了一些内存,这些内存不会与其他进程的部分相交。但我不清楚,这个分配的内存是否也可用于底层 c 函数,或者这些函数可以访问另一个可能相交的字段

我正在根据您的评论猜测答案 - 如果它是错误的,那么我会删除它,但我认为它很可能是正确的,值得作为答案发布。

Python 有一个称为全局解释器锁(或 GIL(的锁定机制。这可确保多个线程不会尝试同时访问同一内存(包括 Python 内部的内存,这对用户来说可能并不明显(。

您的 Cython 代码将假设其线程包含 GIL。我强烈怀疑这不是真的,因此对 Python 对象执行任何操作都可能导致崩溃。处理此问题的一种方法是遵循调用 Cython 代码的 C 代码中的这一部分文档。但是,我怀疑在Cython中更容易处理。

首先告诉 Cython 函数是 "nogil" - 它不需要 GIL:

cdef void default_listener(const event_data_t* data, int data_count, void* user_data) nogil:

如果您现在尝试编译,它将失败,因为您在函数中使用 Python 类型。要解决此问题,请在您的 Cython 代码中声明 GIL。

cdef void default_listener(...) nogil:
with gil:
default_listener_impl(...)

我所做的是将实现放在一个需要 GIL 的单独函数中(即没有附加nogil(。这样做的原因是您不能将cdef语句放在with gil部分中(正如您在评论中所说(-它们必须在该部分之外。但是,您不能将cdef dict放在它之外,因为它是一个 Python 对象。因此,单独的函数是最简单的解决方案。单独的函数看起来几乎和现在default_listener一模一样。


值得一提的是,这不是一个完整的锁定机制 - 它实际上只是为了保护 Python 内部不被损坏 - 一个普通的 Python 线程会定期自动释放并重新获得 GIL,这可能是在您"期间"操作时。除非您告诉 CYTHON 不会释放 GIL(在这种情况下,在with gil:块的末尾(,因此在此期间确实持有独占锁。如果您需要更精细地控制锁定,那么您可能需要查看multithreading库或包装一些 C 锁定库。

最新更新