多处理和垃圾收集

时间:2012-03-31 20:51:02

标签: python unix garbage-collection multiprocessing

在py2.6 +中,multiprocessing模块提供了Pool类,因此可以这样做:

class Volatile(object):
    def do_stuff(self, ...):
        pool = multiprocessing.Pool()
        return pool.imap(...)

但是,使用2.7.2的标准Python实现,这种方法很快就会导致“IOError:[Errno 24]打开的文件过多”。显然,pool对象永远不会被垃圾收集,因此它的进程永远不会终止,累积内部打开的任何描述符。我认为这是因为以下工作:

class Volatile(object):
    def do_stuff(self, ...):
        pool = multiprocessing.Pool()
        result = pool.map(...)
        pool.terminate()
        return result

我想保留imap的“懒惰”迭代器方法;在这种情况下垃圾收集器如何工作?如何修复代码?

3 个答案:

答案 0 :(得分:8)

最后,我最终传递了pool引用并在pool.imap迭代器完成后手动终止它:

class Volatile(object):
    def do_stuff(self, ...):
        pool = multiprocessing.Pool()
        return pool, pool.imap(...)

    def call_stuff(self):
        pool, results = self.do_stuff()
        for result in results:
            # lazy evaluation of the imap
        pool.terminate()

如果将来有人偶然发现这个解决方案:chunksize参数在Pool.imap中非常非常重要(而不是普通Pool.map,那里无关紧要)。我手动设置它,以便每个进程接收1 + len(input) / len(pool)个作业。将它保留为默认chunksize=1给了我相同的性能,好像我根本没有使用并行处理......糟糕。

我想使用有序imap与有序map没有什么好处,我个人更喜欢迭代器。

答案 1 :(得分:5)

在python中,你基本上不能保证什么时候会被破坏,在这种情况下,这不是多处理池的设计方式。

正确的做法是在多个函数调用之间共享一个池。最简单的方法是将池存储为类(或可能是实例)变量:

class Dispatcher:
    pool = multiprocessing.Pool()
    def do_stuff(self, ...):
        result = self.pool.map(...)
        return result

答案 2 :(得分:2)

实际上,即使删除了对pool对象的所有用户引用,并且队列代码中没有任务,并且所有垃圾收集都已完成,那么<​​strong>进程仍然是不可用的僵尸在操作系统中 - 加上Pool挂起的3个僵尸服务线程(Python 2.7和3.4):

>>> del pool
>>> gc.collect()
0
>>> gc.garbage
[]
>>> threading.enumerate()
[<_MainThread(MainThread, started 5632)>, <Thread(Thread-8, started daemon 5252)>, 
 <Thread(Thread-9, started daemon 5260)>, <Thread(Thread-7, started daemon 7608)>]

进一步Pool()将添加越来越多的进程和线程僵尸......一直持续到主进程终止。

它需要一个特殊的戳来阻止这样的僵尸池 - 通过它的服务线程_handle_workers

>>> ths = threading.enumerate()
>>> for th in ths: 
...     try: th.name, th._state, th._Thread__target
...     except AttributeError: pass
...     
('MainThread', 1, None)
('Thread-8', 0, <function _handle_tasks at 0x01462A30>)
('Thread-9', 0, <function _handle_results at 0x014629F0>)
('Thread-7', 0, <function _handle_workers at 0x01462A70>)
>>> ths[-1]._state = multiprocessing.pool.CLOSE  # or TERMINATE
>>> threading.enumerate()
[<_MainThread(MainThread, started 5632)>]
>>> 

终止其他服务线程并终止子进程。

我认为一个问题是,Python库中存在资源泄漏错误,可以通过正确使用 weakref &#来修复39; S

另一点是 Pool创造&amp;终止是昂贵的(包括每个池只有3个服务线程用于管理!),并且根据另一个限制,通常没有理由比CPU核心(高CPU负载)或超过有限数量的工作进程多得多资源(例如网络带宽)。因此,将一个池更像是一个单一的应用程序全局资源(可选地由超时管理)而不是一个只是由一个闭包(或一个terminate() - 持有的快速对象来处理它是合理的,因为这个bug )。

例如:

try:
    _unused = pool   # reload safe global var
except NameError:
    pool = None

def get_pool():
    global pool
    if pool is None:
        atexit.register(stop_pool)
        pool = Pool(CPUCORES)
    return pool

def stop_pool():
    global pool
    if pool:
        pool.terminate()
        pool = None