多处理中的共享内存

我有三个大名单。 首先包含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个subprocess:

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

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

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

因此,我会假设子stream程不需要也不会复制这些庞大的列表,而只是将它们与父级分享。 也就是说,由于linux下的copy-on-write方法,程序会占用16GB的内存(无论启动多less个subprocess)? 我是正确的,还是我错过了会导致列表被复制的东西?

编辑 :我仍然困惑,在阅读了更多的主题后。 一方面,Linux使用copy-on-write,这意味着没有数据被复制。 另一方面,访问对象将改变它的ref-count(我仍然不确定为什么,这是什么意思)。 即使如此,整个对象是否会被复制?

例如,如果我定义一些function,如下所示:

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

使用这个函数是否意味着l1,l2和l3将被完全复制到每个subprocess?

有没有办法检查这个?

编辑2在读取更多内容并在subprocess运行时监视系统的总内存使用情况时,看起来整个对象确实被复制用于每个subprocess。 而这似乎是因为引用计数。

对于l1,l2和l3的引用计数实际上在我的程序中是不需要的。 这是因为l1,l2和l3将保留在内存中(不变),直到父进程退出。 直到那时,没有必要释放这些列表所使用的内存。 事实上,我知道引用计数将保持在0以上(对于这些列表和列表中的每个对象),直到程序退出。

所以现在的问题变成了,我怎样才能确保对象不会被复制到每个subprocess? 我可以禁用这些列表和列表中的每个对象的引用计数?

编辑3只是一个额外的说明。 子stream程不需要修改l1l2l3或这些列表中的任何对象。 子stream程只需要能够引用这些对象中的一些而不导致为每个子stream程复制存储器。

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

  • multithreading
  • 共享内存

Python的multithreading不适合CPU绑定的任务(因为GIL),所以在这种情况下通常的解决scheme是进行multiprocessing 。 但是,使用此解决scheme,您需要使用multiprocessing.Valuemultiprocessing.Array显式共享数据。

请注意,通常在进程之间共享数据可能不是最好的select,因为所有的同步问题; 涉及演员交换信息的方法通常被认为是更好的select。 另请参阅Python文档 :

如上所述,在进行并发编程时,通常最好尽可能避免使用共享状态。 使用多个进程时尤其如此。

但是,如果您确实需要使用某些共享数据,则多处理提供了一些方法。

在你的情况下,你需要通过multiprocessing (比如使用一个multiprocessing.Array )以某种方式来包装l1l2l3 ,然后把它们作为parameter passing。
还要注意,正如你所说的,你不需要写入权限,那么在创build对象的时候你应该传递lock=False ,否则所有的访问都会被序列化。

如果你想使用写时复制function,并且你的数据是静态的(在subprocess中没有改变) – 你应该让python不要混淆数据所在的内存块。 你可以很容易地做到这一点,使用C或C ++结构(例如stl)作为容器,并提供自己的python包装,将使用指针到数据内存(或可能复制数据mem)当python级别的对象将被创build。 所有这些都可以通过使用cython几乎简单的python和语法来完成 。

 #伪cython
 cdef类FooContainer:
    cdef char *数据
    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):
       返回self.data

 #python部分
从富importFooContainer

 f = FooContainer(“hello world”)
 pid = fork()
如果不是pid:
    f.get()#这个调用将读取同一个内存页面到哪里
            #父进程写了1024个字符的self.data
            #和cython会自动创build一个新的pythonstring
            #从它的对象,并返回给调用者

上面的伪代码写得不好。 不要使用它。 在你的情况下,代替self.data应该是C或C ++容器。