[英]Garbage collector tries to collect shared memory object
我有兩個Python腳本在本質上都應該做同樣的事情:在內存中抓取一個大對象,然后派生一堆子對象。 第一個腳本使用裸os.fork
:
import time
import signal
import os
import gc
gc.set_debug(gc.DEBUG_STATS)
class GracefulExit(Exception):
pass
def child(i):
def exit(sig, frame):
raise GracefulExit("{} out".format(i))
signal.signal(signal.SIGTERM, exit)
while True:
time.sleep(1)
if __name__ == '__main__':
workers = []
d = {}
for i in xrange(30000000):
d[i] = i
for i in range(5):
pid = os.fork()
if pid == 0:
child(i)
else:
print pid
workers.append(pid)
while True:
wpid, status = os.waitpid(-1, os.WNOHANG)
if wpid:
print wpid, status
time.sleep(1)
第二個腳本使用multiprocessing
模塊。 我都在Linux(Ubuntu 14.04)上運行,因此它也應該在os.fork
使用os.fork
,如文檔所述:
import multiprocessing
import time
import signal
import gc
gc.set_debug(gc.DEBUG_STATS)
class GracefulExit(Exception):
pass
def child(i):
def exit(sig, frame):
raise GracefulExit("{} out".format(i))
signal.signal(signal.SIGTERM, exit)
while True:
time.sleep(1)
if __name__ == '__main__':
workers = []
d = {}
for i in xrange(30000000):
d[i] = i
for i in range(5):
p = multiprocessing.Process(target=child, args=(i,))
p.start()
print p.pid
workers.append(p)
while True:
for worker in workers:
if not worker.is_alive():
worker.join()
time.sleep(1)
這兩個腳本之間的區別如下:當我殺死一個孩子(發送SIGTERM)時,裸叉腳本會嘗試對共享字典進行垃圾回收,盡管事實上它仍被父進程引用並且實際上沒有被復制到孩子的記憶(由於寫時復制)
kill <pid>
Traceback (most recent call last):
File "test_mp_fork.py", line 33, in <module>
child(i)
File "test_mp_fork.py", line 19, in child
time.sleep(1)
File "test_mp_fork.py", line 15, in exit
raise GracefulExit("{} out".format(i))
__main__.GracefulExit: 3 out
gc: collecting generation 2...
gc: objects in each generation: 521 3156 0
gc: done, 0.0024s elapsed.
( perf record -e page-faults -g -p <pid>
輸出:)
+ 99,64% python python2.7 [.] PyInt_ClearFreeList
+ 0,15% python libc-2.19.so [.] vfprintf
+ 0,09% python python2.7 [.] 0x0000000000144e90
+ 0,06% python libc-2.19.so [.] strlen
+ 0,05% python python2.7 [.] PyArg_ParseTupleAndKeywords
+ 0,00% python python2.7 [.] PyEval_EvalFrameEx
+ 0,00% python python2.7 [.] Py_AddPendingCall
+ 0,00% python libpthread-2.19.so [.] sem_trywait
+ 0,00% python libpthread-2.19.so [.] __errno_location
盡管基於多處理的腳本沒有做到這一點:
kill <pid>
Process Process-3:
Traceback (most recent call last):
File "/usr/lib/python2.7/multiprocessing/process.py", line 258, in _bootstrap
self.run()
File "/usr/lib/python2.7/multiprocessing/process.py", line 114, in run
self._target(*self._args, **self._kwargs)
File "test_mp.py", line 19, in child
time.sleep(1)
File "test_mp.py", line 15, in exit
raise GracefulExit("{} out".format(i))
GracefulExit: 2 out
( perf record -e page-faults -g -p <pid>
輸出:)
+ 62,96% python python2.7 [.] 0x0000000000047a5b
+ 32,28% python python2.7 [.] PyString_Format
+ 2,65% python python2.7 [.] Py_BuildValue
+ 1,06% python python2.7 [.] PyEval_GetFrame
+ 0,53% python python2.7 [.] Py_AddPendingCall
+ 0,53% python libpthread-2.19.so [.] sem_trywait
我還可以通過在引發GracefulExit
之前顯式調用gc.collect()
在基於多處理的腳本上強制執行相同的行為。 奇怪的是,事實並非如此:調用gc.disable(); gc.set_threshold(0)
裸機腳本中的gc.disable(); gc.set_threshold(0)
不能擺脫PyInt_ClearFreeList
調用。
到實際問題:
夫妻事
在python中,多個python 進程意味着多個解釋器具有自己的GIL,GC等
d
字典不作為進程的參數傳遞,它是全局共享變量。
之所以收集它,是因為每個進程都認為它是唯一持有對其引用的引用,嚴格來說,這是正確的,因為它是對字典的單個全局共享對象引用。
當Python GC檢查它時,它將檢查該對象的ref計數器。 由於只有一個共享引用,因此刪除該引用將意味着ref count == 0
,因此將其收集起來。
要解決此問題,應將d
傳遞到每個分支的進程中,使每個進程對其都有自己的引用。
多重處理的行為有所不同,因為它使用os._exit
而不調用退出處理程序,這顯然涉及垃圾收集( 有關本主題的更多內容 )。 在腳本的裸叉版本中顯式調用os._exit
可獲得相同的結果。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.