如何使用子进程模块杀死(或避免)僵尸进程

时间:2010-05-03 19:29:32

标签: python subprocess

当我使用子进程模块从另一个python脚本中启动python脚本时,子进程“完成”时会创建一个僵尸进程。除非我杀死我的父python进程,否则我无法终止此子进程。

有没有办法杀死子进程而不杀死父进程?我知道我可以通过使用wait()来实现这一点,但我需要使用no_wait()运行我的脚本。

9 个答案:

答案 0 :(得分:23)

不使用Popen.communicate()call()会导致僵尸进程。

如果您不需要输出命令,可以使用subprocess.call()

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

如果输出很重要,则应使用Popen()communicate()来获取stdout和stderr。

>>> 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

答案 1 :(得分:19)

僵尸进程不是一个真正的过程;它只是进程表中的剩余条目,直到父进程请求子进程返回代码。实际流程已结束,不需要其他资源,只需要表示流程表条目。

我们可能需要有关您运行的流程的更多信息才能真正提供更多帮助。

但是,如果您的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的子代(即已完成的代码)。

答案 2 :(得分:15)

如果删除子进程对象,使用 del 强制进行垃圾回收,这将导致删除子进程对象,然后失效进程将在不终止解释器的情况下消失。你可以先在python命令行界面试试这个。

答案 3 :(得分:6)

如果你只是使用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()

您可以对Popen返回的对象使用.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()

答案 4 :(得分:5)

一旦他们的进程对象被垃圾收集,python运行时负责摆脱僵尸进程。如果你看到僵尸躺在它周围意味着你保留了一个进程对象,而不是等待,轮询或终止它。

答案 5 :(得分:1)

我不确定你的意思是“我需要用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

答案 6 :(得分:0)

我不完全确定no_wait()的意思。你的意思是你不能阻止等待子进程完成?假设如此,我认为这会做你想做的事情:

os.wait3(os.WNOHANG)

答案 7 :(得分:0)

最近,由于我的python脚本,我遇到了这个僵尸问题。实际问题主要是由于子进程被杀,而父进程并不知道孩子已经死了。所以我做的是,只是在子进程的kill信号之后添加popen.communicate(),以便父进程知道子进程已死,然后内核更新子进程的pid,因为这个子进程已经没有了所以现在没有形成僵尸。

PS:民意调查也是一个选项,因为它检查并传达了父母的子女身份。通常在子进程中,如果你不需要与stdout和stdin通信,你可以使用check_output或调用。

答案 8 :(得分:0)

赞:
s = Popen(args)
s.terminate()
time.sleep(0.5)
s.poll()

有效
僵尸进程将消失