Как убить (или избежать) процессы зомби с помощью модуля подпроцесса

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

Есть ли способ убить подпроцесс, не убивая родителя? Я знаю, что могу сделать это с помощью wait(), но мне нужно запустить мой скрипт с помощью no_wait().

10 ответов

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

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

Однако, если ваша Python-программа знает, когда завершены дочерние процессы (например, достигнув конца дочерних данных stdout), тогда вы можете безопасно вызвать process.wait():

import subprocess

process= subprocess.Popen( ('ls', '-l', '/tmp'), stdout=subprocess.PIPE)

for line in process.stdout:
        pass

subprocess.call( ('ps', '-l') )
process.wait()
print "after wait"
subprocess.call( ('ps', '-l') )

Пример вывода:

$ python so2760652.py
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S   501 21328 21326  0  80   0 -  1574 wait   pts/2    00:00:00 bash
0 S   501 21516 21328  0  80   0 -  1434 wait   pts/2    00:00:00 python
0 Z   501 21517 21516  0  80   0 -     0 exit   pts/2    00:00:00 ls <defunct>
0 R   501 21518 21516  0  80   0 -   608 -      pts/2    00:00:00 ps
after wait
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S   501 21328 21326  0  80   0 -  1574 wait   pts/2    00:00:00 bash
0 S   501 21516 21328  0  80   0 -  1467 wait   pts/2    00:00:00 python
0 R   501 21519 21516  0  80   0 -   608 -      pts/2    00:00:00 ps

В противном случае, вы можете сохранить всех детей в списке, и время от времени .poll для их кодов возврата. После каждой итерации не забудьте удалить из списка детей с кодами возврата, отличными от None (т.е. готовые).

Не используется Popen.communicate() или же call() приведет к процессу зомби.

Если вам не нужен вывод команды, вы можете использовать subprocess.call():

>>> import subprocess
>>> subprocess.call(['grep', 'jdoe', '/etc/passwd'])
0

Если вывод важен, вы должны использовать Popen() а также communicate() чтобы получить стандартный вывод и стандартный вывод.

>>> from subprocess import Popen, PIPE
>>> process = Popen(['ls', '-l', '/tmp'], stdout=PIPE, stderr=PIPE)
>>> stdout, stderr = process.communicate()
>>> stderr
''
>>> print stdout
total 0
-rw-r--r-- 1 jdoe jdoe 0 2010-05-03 17:05 bar
-rw-r--r-- 1 jdoe jdoe 0 2010-05-03 17:05 baz
-rw-r--r-- 1 jdoe jdoe 0 2010-05-03 17:05 foo

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

Если вы просто используете subprocess.Popen все будет хорошо - вот как:

import subprocess

def spawn_some_children():
    subprocess.Popen(["sleep", "3"])
    subprocess.Popen(["sleep", "3"])
    subprocess.Popen(["sleep", "3"])

def do_some_stuff():
    spawn_some_children()
    # do some stuff
    print "children went out to play, now I can do my job..."
    # do more stuff

if __name__ == '__main__':
    do_some_stuff()

Ты можешь использовать .poll() на объекте, возвращенном Попеном, чтобы проверить, завершен ли он (без ожидания). Если он вернется None, ребенок все еще бежит.

Убедитесь, что вы не сохраняете ссылки на объекты Popen - если вы это сделаете, они не будут собирать мусор, так что вы получите зомби. Вот пример:

import subprocess

def spawn_some_children():
    children = []
    children.append(subprocess.Popen(["sleep", "3"]))
    children.append(subprocess.Popen(["sleep", "3"]))
    children.append(subprocess.Popen(["sleep", "3"]))
    return children

def do_some_stuff():
    children = spawn_some_children()
    # do some stuff
    print "children went out to play, now I can do my job..."
    # do more stuff

    # if children finish while we are in this function,
    # they will become zombies - because we keep a reference to them

В приведенном выше примере, если вы хотите избавиться от зомби, вы можете либо .wait() на каждого из детей или .poll() пока результат не None,

В любом случае это нормально - либо не хранить ссылки, либо использовать .wait() или же .poll() ,

Среда выполнения Python берет на себя ответственность за избавление от зомби-процесса после того, как их объекты процесса были собраны сборщиком мусора. Если вы видите зомби, лежащего вокруг, это означает, что вы сохранили объект процесса и не вызывали его, подождите или опросите его.

Я не уверен, что вы имеете в виду "мне нужно запустить мой скрипт с no_wait()", но я думаю, что этот пример делает то, что вам нужно. Процессы не будут зомби очень долго. Родительский процесс будет только wait() на них, когда они на самом деле уже уничтожены, и поэтому они быстро разомкнутся.

#!/usr/bin/env python2.6
import subprocess
import sys
import time

children = []
#Step 1: Launch all the children asynchronously
for i in range(10):
    #For testing, launch a subshell that will sleep various times
    popen = subprocess.Popen(["/bin/sh", "-c", "sleep %s" % (i + 8)])
    children.append(popen)
    print "launched subprocess PID %s" % popen.pid

#reverse the list just to prove we wait on children in the order they finish,
#not necessarily the order they start
children.reverse()
#Step 2: loop until all children are terminated
while children:
    #Step 3: poll all active children in order
    children[:] = [child for child in children if child.poll() is None]
    print "Still running: %s" % [popen.pid for popen in children]
    time.sleep(1)

print "All children terminated"

Вывод к концу выглядит так:

Still running: [29776, 29774, 29772]
Still running: [29776, 29774]
Still running: [29776]
Still running: []
All children terminated

Как это:
s = Popen(args)
s.terminate()
time.sleep(0.5)
s.poll()

он работает
зомби процессы исчезнут

Когда вам не нужно ждать каких-либо порожденных вами подпроцессов, самое простое решение для предотвращения зомби-процессов — это вызватьsignal(SIGCHLD, SIG_IGN);во время инициализации. Затем завершенные подпроцессы немедленно удаляются . Этот параметр применяется ко всему процессу, поэтому вы можете использовать его только в том случае, если вам не нужно ждать ни одного дочернего элемента.

В Питоне:

      import signal
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
# …
# call subprocess.Popen(…) as needed

Недавно я столкнулся с этой проблемой зомби из-за моего скрипта на python. Фактическая проблема была главным образом из-за уничтожения подпроцесса, и родительский процесс не знает, что ребенок мертв. Так что я сделал, просто добавив popen.communicate() после сигнала kill дочернего процесса, чтобы родительский процесс узнал, что дочерний процесс мертв, затем ядро ​​обновляет pid дочернего процесса, так как дочернего процесса больше нет, и так что зомби сейчас не образовалось.

PS: опрос также вариант здесь, так как он проверяет и сообщает о статусе ребенка родителю. Часто в подпроцессе лучше, если вы используете check_output или вызываете, если вам не нужно общаться с stdout и stdin.

Я не совсем уверен, что вы подразумеваете под no_wait(), Вы хотите сказать, что не можете заблокировать ожидание завершения дочерних процессов? Предполагая, что так, я думаю, это будет делать то, что вы хотите:

os.wait3(os.WNOHANG)
Другие вопросы по тегам