涉及C对象的Python多进程共享内存问题

4

我正在开发一个程序,使用外部C库来解析外部数据,并使用Python库对其进行优化。优化非常耗时,因此使用多个CPU将是一个重要的优势。

基本上,我使用Cython将C(++)结构进行了包装,具体如下:

cdef class CObject(object):

    cdef long p_sthg
    cdef OBJECT* sthg

    def __cinit__(self, sthg):
        self.p_sthg = sthg
        self.sthg = <OBJECT*> self.p_sthg

    def __reduce__(self):
        return (rebuildObject, (self.p_sthg, ))

    def getManyThings(self):
        ...
        return blahblahblah

然后我创建了我的资源密集型进程:

p = mp.Process(target=make_process, args=((cobject,)))

你可以立即想到(当然我没有),即使我成功解封CObject,指针被传递到新进程,但它所指向的C结构体并没有被传递。

我可以找到一些资源,说明如何将Python对象放入共享内存,但这在我的情况下是不够的,因为我需要在Python进程之间共享我几乎不了解的C对象(以及由顶部CObject指向的其他对象)。

如果有关系的话,好消息是我可以通过只读访问来生存...

有人在这方面有任何经验吗?
我的另一个想法是找到一种方法将我需要传递的对象的二进制表示写入文件,并从另一个进程中读取它...

1个回答

4
没有单一、通用的方法来实现这个。
你可以通过在适当的mmap(2)区域内构造C对象将其放入共享内存中(Python标准库的mmap也提供此功能;使用MAP_SHARED|MAP_ANONYMOUS)。这要求整个对象都要位于mmap内,可能会使对象无法使用指针(但相对于对象的偏移量可能是可以的,只要它们指向mmap内即可)。如果对象具有任何文件描述符或其他句柄,那么几乎肯定不能正常工作。请注意,mmap()就像malloc()一样;必须进行对应的munmap()操作,否则会导致内存泄漏。
你可以将C对象复制到共享内存中(例如使用memcpy(3))。这可能不太高效,并且需要对象具备合理可复制性。 memcpy 不能魔法般地修复指针和其他引用。好处是这不要求你控制对象的构造。
你可以将对象序列化为某些二进制表示,并通过pipe(2)传递它(在Python中也可以通过os.pipe()实现)。对于简单的情况,这是一次按字段复制,但再次提醒,指针需要注意。你将需要(反)扭曲指针以使它们在(反)序列化后正确工作。这是最易于推广的技术,但需要了解对象的结构,或者使用一个为你执行序列化的黑盒函数。
最后,你可以在/dev/shm中创建临时文件并以此交换信息。这些文件由RAM支持,与共享内存实际上相同,但可能具有更熟悉的类似文件的接口。但这仅适用于Unix系统。在除Linux之外的系统上,应使用shm_open(3)以实现完全的可移植性。
请注意,通常情况下,共享内存往往存在问题。它需要进程间同步,但必要的锁定原语远不如线程世界发展得成熟。我建议将共享内存限制为不可变对象或固有无锁设计(这相当难以做到正确)。

我的结构并不复杂,但是链接到其他结构的std::list,它们本身又链接到其他结构的std::list(我没有检查递归的级别,但没有循环...)。我猜Cython没有被设计来欺骗分配器(用boost找到了一些东西,但没有更基本的?)并手动调整剩余指针...我可能有一个特定于我的需求的解决方法(通过从进程中调用数据获取并修剪我需要的内容,更安全!),但仍然希望看到一个在Cython中通过共享内存传递隐藏的std::list<simple>的测试用例... - xoolive
@xoolive:列表由许多指针组成,大多数封装。如果您尝试共享它,这些指针将无法工作,整个事情都会炸掉。显然Boost可以做到这一点,但这是一个完全额外的依赖项,看起来似乎不能直接在STL容器上工作。递归遍历列表并逐个序列化其元素可能更简单。 - Kevin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接