Обеспечение остановки подпроцессов при выходе из программы Python

Есть ли способ гарантировать, что все созданные подпроцессы не работают во время выхода программы Python? Под подпроцессом я подразумеваю те, которые созданы с помощью subprocess.Popen ().

Если нет, должен ли я перебрать все убийства и затем убить -9? что-нибудь более чистое?


person pupeno    schedule 26.11.2008    source источник
comment
связанные: Как завершить подпроцесс python, запущенный с shell = True   -  person jfs    schedule 22.03.2014
comment


Ответы (14)


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

atexit.register (func [, * args [, ** kargs]])

В процессе очистки вы также можете реализовать собственное ожидание и убить его, когда наступит желаемый тайм-аут.

>>> import atexit
>>> import sys
>>> import time
>>> 
>>> 
>>>
>>> def cleanup():
...     timeout_sec = 5
...     for p in all_processes: # list of your processes
...         p_sec = 0
...         for second in range(timeout_sec):
...             if p.poll() == None:
...                 time.sleep(1)
...                 p_sec += 1
...         if p_sec >= timeout_sec:
...             p.kill() # supported from python 2.6
...     print 'cleaned up!'
...
>>>
>>> atexit.register(cleanup)
>>>
>>> sys.exit()
cleaned up!

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

Следующий метод Windows больше не нужен для python> = 2.6

Вот способ убить процесс в Windows. Ваш объект Popen имеет атрибут pid, поэтому вы можете просто вызвать его с помощью success = win_kill (p.pid) (требуется установлен pywin32):

    def win_kill(pid):
        '''kill a process by specified PID in windows'''
        import win32api
        import win32con

        hProc = None
        try:
            hProc = win32api.OpenProcess(win32con.PROCESS_TERMINATE, 0, pid)
            win32api.TerminateProcess(hProc, 0)
        except Exception:
            return False
        finally:
            if hProc != None:
                hProc.Close()

        return True
person monkut    schedule 26.11.2008
comment
Не могли бы вы немного объяснить, что вы делаете в коде Windows. - person Roshan Mehta; 28.05.2014
comment
Зачем нужен win_kill, если существует p.kill ()? Это для пользователей Python до версии 2.6? - person D. A.; 10.03.2016
comment
Да, я считаю, что в то время 2.5 все еще широко использовалась, а p.kill () не был доступен в Windows. - person monkut; 10.03.2016
comment
Этот ответ можно упростить для Python 3.3+, где Popen.wait() принимает параметр timeout. Первый цикл через вызов p.teminate() подпроцессов (запрос постепенного завершения работы). Затем вызовите wait() для каждого процесса с установленным таймаутом и вызовите p.kill(), если возникнет исключение истечения тайм-аута. - person ncoghlan; 18.11.2020

В * nix, возможно, вам может помочь использование групп процессов - вы также можете поймать подпроцессы, порожденные вашими подпроцессами.

if __name__ == "__main__":
  os.setpgrp() # create new process group, become its leader
  try:
    # some code
  finally:
    os.killpg(0, signal.SIGKILL) # kill all processes in my group

Еще одно соображение - повысить уровень сигналов: от SIGTERM (сигнал по умолчанию для kill) до SIGKILL (он же kill -9). Подождите немного между сигналами, чтобы дать процессу возможность корректно завершиться, прежде чем вы kill -9 его.

person orip    schedule 26.11.2008
comment
Похоже, эта стратегия включает родительский процесс в группу процессов. Поэтому, когда вы отправляете SIGTERM группе процессов, родитель также получает его. Это может быть нежелательно (как было в моем потоке). - person drootang; 24.02.2020

subprocess.Popen.wait() - единственный способ убедиться, что они мертвы. Действительно, операционные системы POSIX требуют, чтобы вы ожидали своих детей. Многие * nix создают процесс «зомби»: мертвый ребенок, которого родитель не дождался.

Если ребенок достаточно хорошо написан, он обрывается. Часто дети читают из PIPE's. Закрытие входа - большой намек для ребенка, что он должен закрыть магазин и выйти.

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

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


Редактировать.

В стандартных ОС у вас есть os.kill( PID, 9 ). Убить -9 сурово, кстати. Если вы можете убить их с помощью SIGABRT (6?) Или SIGTERM (15), это более вежливо.

В ОС Windows у вас os.kill не работает. Посмотрите на этот рецепт ActiveState для завершения процесса в Windows.

У нас есть дочерние процессы, которые являются серверами WSGI. Чтобы завершить их, мы выполняем GET по специальному URL-адресу; это заставляет ребенка убраться и выйти.

person S.Lott    schedule 26.11.2008

Найдите решение для linux (без установки prctl):

def _set_pdeathsig(sig=signal.SIGTERM):
    """help function to ensure once parent process exits, its childrent processes will automatically die
    """
    def callable():
        libc = ctypes.CDLL("libc.so.6")
        return libc.prctl(1, sig)
    return callable


subprocess.Popen(your_command, preexec_fn=_set_pdeathsig(signal.SIGTERM)) 
person Patrick    schedule 01.04.2017

Предупреждение: только для Linux! Вы можете заставить вашего ребенка получать сигнал, когда его родитель умирает.

Сначала установите python-prctl == 1.5.0, затем измените родительский код для запуска дочерних процессов следующим образом

subprocess.Popen(["sleep", "100"], preexec_fn=lambda: prctl.set_pdeathsig(signal.SIGKILL))

Это говорит следующее:

  • запуск подпроцесса: сон 100
  • после разветвления и до выполнения подпроцесса дочерний элемент регистрируется для «отправки мне SIGKILL, когда мой родитель завершает работу».
person Carl D'Halluin    schedule 04.12.2014

Мне нужен был небольшой вариант этой проблемы (очистка подпроцессов, но без выхода из самой программы Python), и поскольку он не упоминается здесь среди других ответов:

p=subprocess.Popen(your_command, preexec_fn=os.setsid)
os.killpg(os.getpgid(p.pid), 15)

setsid запустит программу в новом сеансе, таким образом назначив новую группу процессов ей и ее дочерним элементам. вызов os.killpg на нем, таким образом, также не приведет к остановке вашего собственного процесса Python.

person berdario    schedule 22.03.2014
comment
Нет, вы не можете ... это изменит сеанс самого процесса, если то, что вам нужно, убивает только детей, это не то, что вы хотите - person berdario; 23.03.2014
comment
Я прочитал вопрос, вы прочитали мой ответ? Я прямо написал, что мне нужен небольшой вариант этой проблемы ... если вы гуглите дочерние элементы для выхода из подпроцесса, это будет первый результат, который вы найдете. Фактически, возможность убить детей, не выходя, является более общей проблемой, чем простое выполнение этого при выходе, поэтому это будет полезно для других людей, наткнувшихся на ту же проблему. - person berdario; 23.03.2014
comment
В часто задаваемых вопросах stackoverflow говорится: What, specifically, is the question asking for? Make sure your answer provides that – or a viable alternative. Я предоставил альтернативный и полезный ответ, а вы тем временем проголосовали против обоих опубликованных мной ответов. Если вы перестанете беспокоить людей, которые пытаются помочь пользователям stackoverflow, я уверен, что все сообщество приветствует это. - person berdario; 23.03.2014

голосование( )

Проверьте, не завершился ли дочерний процесс. Возвращает атрибут кода возврата.

person Igal Serban    schedule 26.11.2008

Ответ orip полезен, но имеет обратную сторону: он убивает ваш процесс и возвращает код ошибки вашему родителю. Я избегал этого вот так:

class CleanChildProcesses:
  def __enter__(self):
    os.setpgrp() # create new process group, become its leader
  def __exit__(self, type, value, traceback):
    try:
      os.killpg(0, signal.SIGINT) # kill all processes in my group
    except KeyboardInterrupt:
      # SIGINT is delievered to this process as well as the child processes.
      # Ignore it so that the existing exception, if any, is returned. This
      # leaves us with a clean exit code if there was no exception.
      pass

А потом:

  with CleanChildProcesses():
    # Do your work here

Конечно, вы можете сделать это с помощью try / except / finally, но вам придется обрабатывать исключительные и неисключительные случаи отдельно.

person Malcolm Handley    schedule 08.01.2015
comment
Разве это не отключает control-c? - person dstromberg; 15.08.2019

Решением для Windows может быть использование API задания win32, например. Как автоматически уничтожать дочерние процессы в Windows?

Вот существующая реализация Python

https://gist.github.com/ubershmekel/119697afba2eaecc6330

person ubershmekel    schedule 22.02.2016

Есть ли способ гарантировать, что все созданные подпроцессы не работают во время выхода программы Python? Под подпроцессом я подразумеваю те, которые созданы с помощью subprocess.Popen ().

Вы можете нарушить инкапсуляцию и проверить, что все процессы Popen завершились, выполнив

subprocess._cleanup()
print subprocess._active == []

Если нет, должен ли я перебрать все убийства и затем убить -9? что-нибудь более чистое?

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

person ddaa    schedule 26.11.2008

Мне действительно нужно было это сделать, но для этого нужно было запускать удаленные команды. Мы хотели иметь возможность останавливать процессы, закрывая соединение с сервером. Кроме того, если, например, вы работаете в python repl, вы можете выбрать запуск в качестве переднего плана, если вы хотите использовать Ctrl-C для выхода.

import os, signal, time

class CleanChildProcesses:
    """
    with CleanChildProcesses():
        Do work here
    """
    def __init__(self, time_to_die=5, foreground=False):
        self.time_to_die = time_to_die  # how long to give children to die before SIGKILL
        self.foreground = foreground  # If user wants to receive Ctrl-C
        self.is_foreground = False
        self.SIGNALS = (signal.SIGHUP, signal.SIGTERM, signal.SIGABRT, signal.SIGALRM, signal.SIGPIPE)
        self.is_stopped = True  # only call stop once (catch signal xor exiting 'with')

    def _run_as_foreground(self):
        if not self.foreground:
            return False
        try:
            fd = os.open(os.ctermid(), os.O_RDWR)
        except OSError:
            # Happens if process not run from terminal (tty, pty)
            return False

        os.close(fd)
        return True

    def _signal_hdlr(self, sig, framte):
        self.__exit__(None, None, None)

    def start(self):
        self.is_stopped = False
        """
        When running out of remote shell, SIGHUP is only sent to the session
        leader normally, the remote shell, so we need to make sure we are sent 
        SIGHUP. This also allows us not to kill ourselves with SIGKILL.
        - A process group is called orphaned when the parent of every member is 
            either in the process group or outside the session. In particular, 
            the process group of the session leader is always orphaned.
        - If termination of a process causes a process group to become orphaned, 
            and some member is stopped, then all are sent first SIGHUP and then 
            SIGCONT.
        consider: prctl.set_pdeathsig(signal.SIGTERM)
        """
        self.childpid = os.fork()  # return 0 in the child branch, and the childpid in the parent branch
        if self.childpid == 0:
            try:
                os.setpgrp()  # create new process group, become its leader
                os.kill(os.getpid(), signal.SIGSTOP)  # child fork stops itself
            finally:
                os._exit(0)  # shut down without going to __exit__

        os.waitpid(self.childpid, os.WUNTRACED)  # wait until child stopped after it created the process group
        os.setpgid(0, self.childpid)  # join child's group

        if self._run_as_foreground():
            hdlr = signal.signal(signal.SIGTTOU, signal.SIG_IGN)  # ignore since would cause this process to stop
            self.controlling_terminal = os.open(os.ctermid(), os.O_RDWR)
            self.orig_fore_pg = os.tcgetpgrp(self.controlling_terminal)  # sends SIGTTOU to this process
            os.tcsetpgrp(self.controlling_terminal, self.childpid)
            signal.signal(signal.SIGTTOU, hdlr)
            self.is_foreground = True

        self.exit_signals = dict((s, signal.signal(s, self._signal_hdlr))
                                 for s in self.SIGNALS)                                     

    def stop(self):
        try:
            for s in self.SIGNALS:
                #don't get interrupted while cleaning everything up
                signal.signal(s, signal.SIG_IGN)

            self.is_stopped = True

            if self.is_foreground:
                os.tcsetpgrp(self.controlling_terminal, self.orig_fore_pg)
                os.close(self.controlling_terminal)
                self.is_foreground = False

            try:
                os.kill(self.childpid, signal.SIGCONT)
            except OSError:
                """
                can occur if process finished and one of:
                - was reaped by another process
                - if parent explicitly ignored SIGCHLD
                    signal.signal(signal.SIGCHLD, signal.SIG_IGN)
                - parent has the SA_NOCLDWAIT flag set 
                """
                pass

            os.setpgrp()  # leave the child's process group so I won't get signals
            try:
                os.killpg(self.childpid, signal.SIGINT)
                time.sleep(self.time_to_die)  # let processes end gracefully
                os.killpg(self.childpid, signal.SIGKILL)  # In case process gets stuck while dying
                os.waitpid(self.childpid, 0)  # reap Zombie child process
            except OSError as e:
                pass
        finally:
            for s, hdlr in self.exit_signals.iteritems():
                signal.signal(s, hdlr)  # reset default handlers

    def __enter__(self):
        if self.is_stopped:
            self.start()

    def __exit__(self, exit_type, value, traceback):
        if not self.is_stopped:
            self.stop()

Спасибо Малкольму Хэндли за первоначальный дизайн. Сделано с помощью python2.7 в Linux.

person matanmarkind    schedule 29.12.2016

Вот что я сделал для своего приложения posix:

Когда ваше приложение существует, вызовите метод kill () этого класса: http://www.pixelbeat.org/libs/subProcess.py

Пример использования здесь: http://code.google.com/p/fslint/source/browse/trunk/fslint-gui#608.

person pixelbeat    schedule 26.11.2008

справка по коду Python: http://docs.python.org/dev/library/subprocess.html#subprocess.Popen.wait

person st4rtx    schedule 06.03.2012
comment
Хотя теоретически это может дать ответ на вопрос, было бы предпочтительнее включить сюда основные части ответа и предоставить ссылку для справки. - person oers; 06.03.2012

Вы можете попробовать subalive, пакет, который я написал для подобной проблемы. Он использует периодический активный пинг через RPC, и подчиненный процесс автоматически завершается, когда главный по какой-то причине прекращает активный пинг.

https://github.com/waszil/subalive

Пример для мастера:

from subalive import SubAliveMaster

# start subprocess with alive keeping
SubAliveMaster(<path to your slave script>)

# do your stuff
# ...

Пример подчиненного подпроцесса:

from subalive import SubAliveSlave

# start alive checking
SubAliveSlave()

# do your stuff
# ...
person waszil    schedule 12.10.2018