Подпроцессы Python для сбора мусора

Вкратце: у меня есть задачи с огромными возвращаемыми значениями, которые потребляют много памяти. Я отправляю их на concurrent.futures.ProcessPoolExecutor. Подпроцессы удерживают память до тех пор, пока не получат новую задачу. Как заставить подпроцессы эффективно собирать мусор?

Пример

import concurrent.futures
import time

executor = concurrent.futures.ProcessPoolExecutor(max_workers=1)

def big_val():
    return [{1:1} for i in range(1, 1000000)]

future = executor.submit(big_val)

# do something with future result

В приведенном выше примере я создаю большой объект в подпроцессе, а затем работаю с результатом. С этого момента я могу иметь дело с памятью в родительском процессе, но подпроцесс, созданный моим ProcessPoolExecutor, будет бесконечно удерживать память, выделенную для моей задачи.

Что я пробовал

Честно говоря, единственное, о чем я могу думать, это отправить фиктивное задание:

def donothing():
    pass

executor.submit(donothing)

Это работает, но а) довольно неуклюже и, что более важно, б) ненадежно, потому что у меня нет гарантий относительно того, в какой подпроцесс я отправляю задачи, поэтому единственный надежный способ — отправить флуд, чтобы убедиться, что подпроцессы, которые мне нужны получить копию.

Насколько я могу судить, как только рабочий процесс завершит выполнение моей задачи, у него нет причин удерживать результат. Если бы мой родительский процесс присвоил возвращенное Future локальной переменной, то в момент завершения задачи возвращаемое значение будет скопировано в Future в родительском процессе, что означает, что рабочему процессу это больше не нужно. Если мой родительский процесс этого не сделал, то возвращаемое значение все равно отбрасывается.

Я что-то неправильно понимаю, или это просто неудачная причуда того, как подпроцессы ссылаются на память? Если да, то есть ли лучший обходной путь?


person daveruinseverything    schedule 09.01.2019    source источник
comment
Почему бы вместо этого не использовать многопроцессорный общий список? stackoverflow.com/a/42490484/524743   -  person Samuel    schedule 09.01.2019


Ответы (1)


Подход с фиктивной задачей — единственный способ добиться этого без значительного рефакторинга кода (чтобы вообще не возвращать огромное значение).

Проблема в том, что рабочий процесс привязывает результат на локальное имя r перед отправкой обратно родительскому объекту и заменяет r только при поступлении новой задачи.

Вы могли бы разумно открыть запрос на улучшение/обработку ошибки в системе отслеживания ошибок CPython, чтобы рабочий процесс явно del r после вызова _sendback_result ; он уже делает это для call_item (упакованная функция и аргументы, отправленные воркеру) точно по той же причине, чтобы не удерживать ресурсы за пределами их окна полезности, и имеет смысл сделать то же самое для уже возвращенных и не более релевантный результат.

person ShadowRanger    schedule 10.01.2019
comment
Спасибо @ShadowRanger, отличный ответ, который не только объясняет проблему, но и указывает, где ее решить в Python. Принято - person daveruinseverything; 11.01.2019