2017-05-28 149 views
2

this question及其答案,我我明白为什么这个Python代码:共享内存和多

big_list = [ 
    {j: 0 for j in range(200000)} 
    for i in range(60) 
] 

def worker(): 
    for dic in big_list: 
     for key in dic: 
      pass 
     print "." 
     time.sleep(0.2) 

w = multiprocessing.Process(target=worker) 
w.start() 

time.sleep(3600) 

保持其执行过程中使用越来越多的内存:这是因为孩子的过程中更新引用计数到循环中的共享内存对象,触发“写时复制”mecanism(我可以通过cat /proc/meminfo | grep MemFree观看可用内存减少)。

我不明白,但是,这就是为什么同样的事情发生,如果反复发生在父母,而不是孩子:

def worker(): 
    time.sleep(3600) 

w = multiprocessing.Process(target=worker) 
w.start() 

for dic in big_list: 
    for key in dic: 
     pass 
    print "." 
    time.sleep(0.2) 

孩子甚至不需要知道存在big_list

在这个小例子中,我可以通过将del big_list放入子函数来解决问题,但有时候变量引用不能像这样访问,所以事情变得复杂。

为什么这种机械化发生,我该如何避免它正常?

+0

您的结果和问题可能是操作系统(Unix/Linux/OSX)的依赖。他们肯定没有正确编码Windows(没有'if __name__ =='__main __':',参见[**主模块**的安全导入](https://docs.python.org/3/library/multiprocessing .html#the-spawn-and-forkserver-start-methods)在文档中)。 – martineau

回答

2

fork()之后,父母和孩子都“看到”相同的地址空间。第一次或者在公共地址更改内存时,写时复制(COW)机制必须克隆包含该地址的页面。因此,为了创建COW页面,突变是发生在孩子还是父母身上并不重要。

在你的第二段代码中,你忽略了最重要的部分:big_list的创建地点。既然你说过你可以在del big_list的孩子身上脱身,big_list可能在你分叉工人过程之前就已经存在了。如果是这样,那么 - 如上所述 - 对于您的症状big_list是否在父母或孩子中被修改并不重要。

为避免这种情况,请在之后创建big_list创建您的子进程。那么它所在的地址空间将不会被共享。或者,在Python 3.4或更高版本中,使用multiprocessing.set_start_method('spawn')。然后fork()将不会用于创建子进程,并且根本没有共享地址空间(Windows上始终是这种情况,它没有fork())。

+0

谢谢你的解释!我尝试使用[billiard](https://github.com/celery/billiard/)在python2中使用'set_start_method('spawn')',但使用billiard.Queue使得进程之间的通信非常缓慢,因此对我用例。我最终做了你的建议:更早的叉(即使在我的情况下,它可能是像真正使用前一小时)。 – fspot