Действительно, даже если все пользовательские ссылки на объект pool
удалены, и в коде очереди нет задач, и вся сборка мусора выполнена, тогда процессы по-прежнему остаются непригодными для использования зомби в операционная система - плюс у нас есть 3 служебных потока зомби из Pool
висели (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
.
Другой момент заключается в том, что Pool
создание и завершение обходятся дорого (включая 3 служебных потока на пул только для управления!), И обычно нет причин иметь гораздо больше рабочих процессов, чем процессорных ядер (высокая загрузка ЦП) или более ограниченного числа в соответствии с другим ограничивающим ресурсом (например, пропускная способность сети). Поэтому разумно рассматривать пул больше как глобальный ресурс единственного приложения (необязательно управляемый тайм-аутом), а не как быстрый объект, удерживаемый замыканием (или прерыванием () - обходной путь из-за ошибки) ,
Например:
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