【发布时间】:2011-02-15 04:22:13
【问题描述】:
当我使用 subprocess 模块从另一个 python 脚本中启动一个 python 脚本时,当子进程“完成”时会创建一个僵尸进程。除非我杀死我的父 python 进程,否则我无法杀死这个子进程。
有没有办法杀死子进程而不杀死父进程?我知道我可以使用 wait() 来做到这一点,但我需要使用 no_wait() 来运行我的脚本。
【问题讨论】:
标签: python subprocess
当我使用 subprocess 模块从另一个 python 脚本中启动一个 python 脚本时,当子进程“完成”时会创建一个僵尸进程。除非我杀死我的父 python 进程,否则我无法杀死这个子进程。
有没有办法杀死子进程而不杀死父进程?我知道我可以使用 wait() 来做到这一点,但我需要使用 no_wait() 来运行我的脚本。
【问题讨论】:
标签: python subprocess
僵尸进程不是真正的进程;它只是进程表中的剩余条目,直到父进程请求子进程的返回码。实际进程已经结束,除了上述进程表条目之外不需要其他资源。
我们可能需要有关您运行的进程的更多信息,以便实际提供更多帮助。
但是,如果您的 Python 程序知道子进程何时结束(例如,通过到达子标准输出数据的末尾),那么您可以安全地调用process.wait(): p>
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() 来获取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
【讨论】:
Popen.poll()可以检查进程是否完成,然后可以使用communicate。这样你就可以保持并发。
call怎么办?
如果您删除子进程对象,使用 del 强制垃圾回收,这将导致子进程对象被删除,然后失效的进程将消失而不会终止您的解释器。你可以先在python命令行界面试试看。
【讨论】:
s = subprocess.Popen('exec cat /dev/zero > /dev/null'),然后是 killall cat,然后是 del s → 僵尸还在。
s.kill() 然后调用del s。没有僵尸。
如果您只使用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()。
【讨论】:
python 运行时负责在其进程对象被垃圾收集后摆脱僵尸进程。如果你看到僵尸在它周围躺着,说明你保留了一个进程对象,而不是在它上面调用等待、轮询或终止。
【讨论】:
.wait()、.poll() 或.terminate(),我猜彼得是正确的。
Popen() 对象的显式引用。它测试“python 运行时是否负责在其进程对象被垃圾收集后摆脱僵尸进程”。有一些处理僵尸的支持:_active list, _cleanup() function is called from Popen() 但测试表明它对 Python 2 没有帮助。
我不确定您的意思是“我需要使用 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
【讨论】:
.wait() might create zombies: 假设您在一个应该在一个小时内终止的进程上调用了.wait(),并且您还有其他 1000 个子进程应该在一个小时内退出第二。繁荣。你在一秒钟内有 1000 个僵尸,直到一个小时过去了才会被收割。
像这样:s = Popen(args)s.terminate()time.sleep(0.5)s.poll()
有效
僵尸进程会消失
【讨论】:
我不完全确定您所说的 no_wait() 是什么意思。你的意思是你不能阻止等待子进程完成?假设是这样,我认为这会做你想要的:
os.wait3(os.WNOHANG)
【讨论】:
最近,由于我的 python 脚本,我遇到了这个僵尸问题。实际问题主要是由于子进程被杀死而父进程不知道孩子已经死了。所以我所做的是,只是在子进程的终止信号之后添加 popen.communicate() 以便父进程知道子进程已经死了,然后内核更新子进程的pid,因为子进程已经不存在了所以现在没有僵尸形成。
PS:poll 在这里也是一个选项,因为它检查子状态并将其传达给父级。通常在子进程中,如果您不需要与标准输出和标准输入通信,最好使用 check_output 或调用。
【讨论】: