2017-05-28 186 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