Memory management 在两个python进程内安全地写入cython c包装器中的变量,或者为python进程提供不同的内存

Memory management 在两个python进程内安全地写入cython c包装器中的变量,或者为python进程提供不同的内存,memory-management,cython,Memory Management,Cython,我正在c库上创建一个包装器,它接收一些财务数据,并希望将其收集到python数据类型中(dict包含字段名列表,列表包含财务数据字段) 在c级,有一个函数开始“监听”某个端口,当出现任何事件时,调用某个用户定义的函数。这个函数是用cython编写的。此类功能的简化示例如下: cdef void default_listener(const event_data_t* data, int data_count, void* user_data): cdef trade_t* trades

我正在c库上创建一个包装器,它接收一些财务数据,并希望将其收集到python数据类型中(dict包含字段名列表,列表包含财务数据字段)

在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,
                                ]
                               )
cdef void default\u侦听器(常量事件数据、整数数据、void*用户数据):
cdef trade_t*trades=数据#根据预期类型转换接收到的数据
cdef dict py_data=用户数据#将用户数据转换为初始类型(在本例中为dict)
对于范围内的i(数据计数):
#附加到传递给函数的dict中的list
#接收结构的字段
py_data['data'].append([trades[i].price,
行业[i].规模,
]
)
问题:当只有一个python进程启动了这个函数时,没有问题,但是如果我启动另一个python进程并运行同一个函数,其中一个进程将在不确定的时间内终止。我认为这是因为在不同进程中同时调用的两个函数可能试图写入内存的同一部分。可能是这样吗

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


注意:我也读过,根据它,每个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\u listener
现在所做的那样



值得一提的是,这并不是一个完整的锁定机制——它实际上只是为了保护Python内部不被破坏——一个普通的Python线程会定期自动释放并重新获得GIL,这可能是在您“执行”操作时发生的。Cython不会释放GIL,除非您告诉它(在本例中,在使用GIL:块的
末尾),因此在这段时间内会持有独占锁。如果您需要更好地控制锁定,那么您可能需要查看
多线程
库,或者包装一些C锁定库。

1)您是如何运行线程/进程的?Python同时支持“多线程”和“多线程处理”。“多线程”共享内存,而“多处理”则不共享,除非您明确告诉它。2) Python已经有了“全局解释器锁”或GIL,它应该可以阻止两个线程同时向同一内存写入数据——所以我认为这不是你的问题。3)一个可能的问题是Cython没有单独释放GIL(除非你特别告诉它),因此它可能会锁定一个单独的Python线程。要对此进行评论,我们需要知道如何设置/调用侦听器函数。我的猜测是这是您的问题,但我没有足够的信息告诉我的代码,既没有多处理,也没有多线程。所有的逻辑都在c库中,如果关键的话,我会找到负责创建“侦听”过程的确切部分。我还可以在bitbucket上提供指向c库源代码的链接,如果它有意义的话。据我所知,应该在启动新线程的函数上设置nogil,它不是
default\u listener
,而是
attach\u listener
,并将
default\u listener
作为参数。因此,我应该将
nogil
添加到
attach\u listener
、添加到
default\u listener
还是同时添加到这两者?
nogil
在函数上只是说它不需要GIL(在调用它时)。在这种情况下,它不需要GIL,因为它会立即请求GIL本身。您肯定希望在
default\u listener
上遵循此模式。很难了解
attach\u listener
——您是从C还是Python调用它?如果您是从C调用它,那么它应该是相同的。如果您是从Python调用它,那么它不应该是。非常感谢您的澄清