首页 文章

多处理中的共享内存

提问于
浏览
52

我有三个大清单 . 首先包含bitarrays(模块bitarray 0.8.0),另外两个包含整数数组 .

l1=[bitarray 1, bitarray 2, ... ,bitarray n]
l2=[array 1, array 2, ... , array n]
l3=[array 1, array 2, ... , array n]

这些数据结构需要相当多的RAM(总共约16GB) .

如果我使用以下内容启动12个子流程:

multiprocessing.Process(target=someFunction, args=(l1,l2,l3))

这是否意味着将为每个子流程复制l1,l2和l3,或者子流程是否会共享这些列表?或者更直接,我会使用16GB或192GB的RAM吗?

someFunction将从这些列表中读取一些值,然后根据读取的值执行一些计算 . 结果将返回到父进程 . someIunction不会修改列表l1,l2和l3 .

因此,我认为子流程不需要也不会复制这些巨大的列表,而只是与父级共享它们 . 这意味着由于linux下的写时复制方法,该程序将占用16GB的RAM(无论我启动多少个子进程)?我是正确的还是我错过了会导致列表被复制的内容?

EDIT :在阅读了关于这个主题的更多内容之后,我仍然感到困惑 . 一方面,Linux使用copy-on-write,这意味着不会复制任何数据 . 另一方面,访问该对象将改变其重新计数(我仍然不确定为什么以及这意味着什么) . 即便如此,是否会复制整个对象?

例如,如果我定义someFunction如下:

def someFunction(list1, list2, list3):
    i=random.randint(0,99999)
    print list1[i], list2[i], list3[i]

是否使用此函数意味着将为每个子进程完全复制l1,l2和l3?

有没有办法检查这个?

EDIT2 在子流程运行的同时阅读了更多内容并监视系统的总内存使用情况后,似乎确实为每个子流程复制了整个对象 . 这似乎是因为引用计数 .

在我的程序中实际上不需要l1,l2和l3的引用计数 . 这是因为l1,l2和l3将保留在内存中(未更改),直到父进程退出 . 在此之前,不需要释放这些列表使用的内存 . 事实上,我确信引用计数将保持在0以上(对于这些列表和这些列表中的每个对象),直到程序退出 .

所以现在问题变成了,我如何确保不会将对象复制到每个子流程?我可以禁用这些列表和这些列表中的每个对象的引用计数吗?

EDIT3 只是一个额外的说明 . 子流程不需要修改 l1l2l3 或这些列表中的任何对象 . 子进程只需要能够引用其中一些对象,而不会导致为每个子进程复制内存 .

3 回答

  • 39

    一般来说,有两种方法可以共享相同的数据:

    • 多线程

    • 共享内存

    Python的多线程不适合CPU绑定任务(因为GIL),因此在这种情况下通常的解决方案是继续使用 multiprocessing . 但是,使用此解决方案,您需要使用multiprocessing.Valuemultiprocessing.Array显式共享数据 .

    请注意,由于所有同步问题,通常在进程之间共享数据可能不是最佳选择;涉及演员交换信息的方法通常被视为更好的选择 . 另见Python documentation

    如上所述,在进行并发编程时,通常最好尽量避免使用共享状态 . 使用多个进程时尤其如此 . 但是,如果您确实需要使用某些共享数据,那么多处理提供了两种方法 .

    在您的情况下,您需要以 multiprocessing 可以理解的某种方式包装 l1l2l3 (例如,通过使用 multiprocessing.Array ),然后将它们作为参数传递 .
    另请注意,正如您所说,您不需要写访问权限,那么您应该在创建对象时传递 lock=False ,否则所有访问仍将被序列化 .

  • 1

    如果你想利用写时复制功能,你的数据是静态的(在子进程中没有改变) - 你应该让python不要弄乱数据所在的内存块 . 您可以通过使用C或C结构(例如stl)作为容器轻松地执行此操作,并提供您自己的python包装器,它将使用指向数据存储器的指针(或者可能复制数据mem),如果有的话,将创建python级别的对象 . 使用cython几乎可以通过简单的python简单和语法完成所有这一切 .

    # pseudo cython
    cdef class FooContainer:
       cdef char * data
       def __cinit__(self, char * foo_value):
           self.data = malloc(1024, sizeof(char))
           memcpy(self.data, foo_value, min(1024, len(foo_value)))
    
       def get(self):
           return self.data
    
    # python part
    from foo import FooContainer
    
    f = FooContainer("hello world")
    pid = fork()
    if not pid:
       f.get() # this call will read same memory page to where
               # parent process wrote 1024 chars of self.data
               # and cython will automatically create a new python string
               # object from it and return to caller
    

    上面的伪代码编写得很糟糕 . 不要用它 . 在你的情况下,代替self.data应该是C或C容器 .

  • 11

    您可以使用memcached或redis并将每个设置为键值对{'l1'...

相关问题