Сборщик мусора в подпроцессах Python - PullRequest
0 голосов
/ 09 января 2019

tl; dr: у меня есть задачи с огромными возвращаемыми значениями, которые занимают много памяти. Я отправляю их на 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 в родительской переменной, что означает, что работник больше не нуждается в этом. Если мой родительский процесс этого не сделал, то возвращаемое значение в любом случае фактически отбрасывается.

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

1 Ответ

0 голосов
/ 10 января 2019

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

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

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

...