多处理中的共享内存
我有三个大名单。 首先包含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程不需要修改l1
, l2
和l3
或这些列表中的任何对象。 子stream程只需要能够引用这些对象中的一些而不导致为每个子stream程复制存储器。
一般来说,共享相同的数据有两种方式:
- multithreading
- 共享内存
Python的multithreading不适合CPU绑定的任务(因为GIL),所以在这种情况下通常的解决scheme是进行multiprocessing
。 但是,使用此解决scheme,您需要使用multiprocessing.Value
和multiprocessing.Array
显式共享数据。
请注意,通常在进程之间共享数据可能不是最好的select,因为所有的同步问题; 涉及演员交换信息的方法通常被认为是更好的select。 另请参阅Python文档 :
如上所述,在进行并发编程时,通常最好尽可能避免使用共享状态。 使用多个进程时尤其如此。
但是,如果您确实需要使用某些共享数据,则多处理提供了一些方法。
在你的情况下,你需要通过multiprocessing
(比如使用一个multiprocessing.Array
)以某种方式来包装l1
, l2
和l3
,然后把它们作为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 ++容器。