[英]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.