在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
的“懒惰”迭代器方法;在这种情况下垃圾收集器如何工作?如何修复代码?
答案 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