簡體   English   中英

垃圾收集器嘗試收集共享內存對象

[英]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想要在進程退出時釋放所有分配的內存,而忽略了子進程並不實際擁有它的事實,但是多處理模塊又為何不這樣做呢?
  • 我想通過裸叉解決方案來實現類似第二腳本的行為(即:不嘗試釋放由父進程分配的內存)(主要是因為我使用的第三方進程管理器庫不t使用多重處理); 我怎么可能那樣做?

夫妻事

  • 在python中,多個python 進程意味着多個解釋器具有自己的GIL,GC等

  • d字典不作為進程的參數傳遞,它是全局共享變量。

之所以收集它,是因為每個進程都認為它是唯一持有對其引用的引用,嚴格來說,這是正確的,因為它是對字典的單個全局共享對象引用。

當Python GC檢查它時,它將檢查該對象的ref計數器。 由於只有一個共享引用,因此刪除該引用將意味着ref count == 0 ,因此將其收集起來。

要解決此問題,應將d傳遞到每個分支的進程中,使每個進程對其都有自己的引用。

多重處理的行為有所不同,因為它使用os._exit而不調用退出處理程序,這顯然涉及垃圾收集( 有關本主題的更多內容 )。 在腳本的裸叉版本中顯式調用os._exit可獲得相同的結果。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM