Как убить (или избежать) процессы зомби с помощью модуля подпроцесса
Когда я запускаю скрипт 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)