2013-05-02 2 views
1

У меня есть приложение JavaScript, работающее на основе Python/PyQt/QtWebKit, которое создает объекты subprocess.Popen для запуска внешних процессов.Как очистить подпроцесс.Появление экземпляров после завершения процесса

Popen объекты хранятся в словаре, и ссылается внутренний идентификатор, так что приложение JS может вызывать методы Popen «с помощью pyqtSlot, таких как poll(), чтобы определить, является ли процесс все еще работает или kill(), чтобы убить изгоев процесса.

Если процесс больше не работает, я хотел бы удалить его объект Popen из словаря для сбора мусора.

Каким будет рекомендованный подход к автоматической очистке словаря, чтобы предотвратить утечку памяти?

Мои идеи до сих пор:

  • вызовов Popen.wait() в потоке на порожденного процесса, чтобы выполнить автоматическую очистку прямо по окончании.
    PRO: Непосредственная очистка, потоки, вероятно, не требуют большой мощности процессора, поскольку они должны спать, не так ли?
    CON: Много потоков в зависимости от нерестовой активности.
  • Используйте поток, чтобы позвонить Popen.poll() обо всех существующих процессах и проверьте returncode, если они в этом случае были завершены и очищены.
    PRO: Только один рабочий поток для всех процессов, более низкий уровень использования памяти.
    CON: необходим периодический опрос, более высокий уровень использования ЦП, если существует много длительных процессов или много обработанных порожденных.

Какой из них вы бы выбрали и почему? Или любые лучшие решения?

+0

Какая операционная система должна будет работать? – Aya

+0

Главным образом Windows, Mac OS X, если возможно, Linux было бы неплохо иметь. Лучшим было бы решение с платформой-агностиком. – Archimedix

+0

Ну, ответ, который я дал, будет работать на Linux и OSX. Я должен подумать о решении Windows. – Aya

ответ

2

Для платформы агностик решение, я бы с опцией # 2, так как «CON» высокой загрузки процессора можно обойти что-то вроде ...

import time 

# Assuming the Popen objects are in the dictionary values 
PROCESS_DICT = { ... } 

def my_thread_main(): 
    while 1: 
     dead_keys = [] 
     for k, v in PROCESS_DICT.iteritems(): 
      v.poll() 
      if v.returncode is not None: 
       dead_keys.append(k) 
     if not dead_keys: 
      time.sleep(1) # Adjust sleep time to taste 
      continue 
     for k in dead_keys: 
      del PROCESS_DICT[k] 

... причем, если никакие процессы не умерли на итерации, вы просто спите немного.

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

Однако существуют лучшие решения, зависящие от платформы.

Для Windows вы должны использовать функцию WaitForMultipleObjects через ctypes как ctypes.windll.kernel32.WaitForMultipleObjects, хотя вам нужно будет изучить возможность.

Для OSX и Linux, возможно, проще всего обрабатывать SIGCHLD асинхронно, используя модуль signal.

Быстрый n 'грязный пример ...

import os 
import time 
import signal 
import subprocess 

# Map child PID to Popen object 
SUBPROCESSES = {} 

# Define handler 
def handle_sigchld(signum, frame): 
    pid = os.wait()[0] 
    print 'Subprocess PID=%d ended' % pid 
    del SUBPROCESSES[pid] 

# Handle SIGCHLD 
signal.signal(signal.SIGCHLD, handle_sigchld) 

# Spawn a couple of subprocesses 
p1 = subprocess.Popen(['sleep', '1']) 
SUBPROCESSES[p1.pid] = p1 
p2 = subprocess.Popen(['sleep', '2']) 
SUBPROCESSES[p2.pid] = p2 

# Wait for all subprocesses to die 
while SUBPROCESSES: 
    print 'tick' 
    time.sleep(1) 

# Done 
print 'All subprocesses died' 
+0

Да, я думал об использовании 'WaitForMultipleObjects()', однако это было бы немного сложно, я думаю ... вам, вероятно, придется обновлять процесс ожидания каждый раз, когда добавляется новый процесс, что, возможно, не стоит усилий, например в цикле и используя время ожидания ожидания в несколько секунд или что-то в этом роде. Кроме того, вам может потребоваться разделить ожидания на несколько потоков из-за ограничения MAXIMUM_WAIT_OBJECTS. – Archimedix

+0

@Archimedix Да. Это очень похоже на использование ['select()'] (http://docs.python.org/2/library/select.html#select.select) в нескольких файловых дескрипторах - обычная идиома заключается в включении FD (обычно прослушивающий сокет), который может потенциально изменить набор FD, который вы контролируете. Поэтому в вашем случае вам нужно включить в набор какой-либо объект, который может быть использован для обнаружения при создании нового процесса, тогда таймаут ожидания может быть очень длинным. Единственным преимуществом, однако, было бы удаление латентности из вашего варианта №2. (продолжение в следующем комментарии) – Aya

+0

@Archimedix (продолжение из предыдущего комментария) Решение 'SIGCHLD' кажется самым элегантным, будучи асинхронным (т. е. не требует блокировки вызовов) и может использоваться в основном потоке. Его также можно использовать в Windows, если ваш код будет работать под версией Python, скомпилированной для [cygwin] (http://www.cygwin.com/), но это, вероятно, будет еще более сложным, используя несколько сторонних модулей расширения Python. Я бы предложил использовать опцию № 2 на данный момент, так как она не требует много управления потоками и, если понадобится, позже будет оптимизирована. – Aya

 Смежные вопросы

  • Нет связанных вопросов^_^