如何使用子进程模块杀死(或避免)僵尸进程
Posted
技术标签:
【中文标题】如何使用子进程模块杀死(或避免)僵尸进程【英文标题】:how to kill (or avoid) zombie processes with subprocess module 【发布时间】:2011-02-15 04:22:13 【问题描述】:当我使用 subprocess 模块从另一个 python 脚本中启动一个 python 脚本时,当子进程“完成”时会创建一个僵尸进程。除非我杀死我的父 python 进程,否则我无法杀死这个子进程。
有没有办法杀死子进程而不杀死父进程?我知道我可以使用 wait() 来做到这一点,但我需要使用 no_wait() 来运行我的脚本。
【问题讨论】:
【参考方案1】:僵尸进程不是真正的进程;它只是进程表中的剩余条目,直到父进程请求子进程的返回码。实际进程已经结束,除了上述进程表条目之外不需要其他资源。
我们可能需要有关您运行的进程的更多信息,以便实际提供更多帮助。
但是,如果您的 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
的子项(即完成的子项)。
【讨论】:
哦,我现在明白了,我的回答基本上是你最后一段的代码版本。 关于僵尸进程的非常有用的信息。您知道此信息是否同样适用于所有平台吗?即僵尸进程只是条目,实际上并没有运行。 @ZoranPavlovic: zombie process 在 POSIX 中是真实存在的:一个已经终止的进程,当它的退出状态被报告给另一个正在等待该进程的进程时被删除终止。 如果可能的话,我会放弃 2 个 ups!【参考方案2】:不使用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
【讨论】:
感谢您的评论。不幸的是,communication 会等待进程完成,然后再生成一个新进程。我需要并行运行多个进程。 call() 等待进程完成。我认为 OP 希望在父进程运行时生成进程并在父进程中执行其他操作。 使用Popen.poll()
可以检查进程是否完成,然后可以使用communicate
。这样你就可以保持并发。
如果因为需要写入进程的stdin而无法调用call
怎么办?
注意,如果您以任何方式使用 Popen 的输出,那么仍然会创建僵尸进程。【参考方案3】:
如果您删除子进程对象,使用 del 强制垃圾回收,这将导致子进程对象被删除,然后失效的进程将消失而不会终止您的解释器。你可以先在python命令行界面试试看。
【讨论】:
这对我不起作用:s = subprocess.Popen('exec cat /dev/zero > /dev/null')
,然后是 killall cat
,然后是 del s
→ 僵尸还在。
它对我有用。在 Celery 中,当引发 SoftTimeLimitExceeded 异常时,我调用s.kill()
然后调用del s
。没有僵尸。
这不适用于Python 2【参考方案4】:
如果您只使用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()
。
【讨论】:
have you checked that your first example doesn't produces zombies if parent is kept alive? 很棒的答案!释放对被终止进程的引用就像一个魅力。【参考方案5】:python 运行时负责在其进程对象被垃圾收集后摆脱僵尸进程。如果你看到僵尸在它周围躺着,说明你保留了一个进程对象,而不是在它上面调用等待、轮询或终止。
【讨论】:
A simple test shows that it is not true on Python 2。它似乎适用于 Python 3 @J.F.Sebastian 你确定这个测试是正确的吗?既然你没有打电话给.wait()
、.poll()
或.terminate()
,我猜彼得是正确的。
@blong:要调用方法,您需要一个名称(或其他引用对象的方式):测试故意不保留对Popen()
对象的显式引用。它测试“python 运行时是否负责在其进程对象被垃圾收集后摆脱僵尸进程”。有一些处理僵尸的支持:_active
list, _cleanup()
function is called from Popen()
但测试表明它对 Python 2 没有帮助。【参考方案6】:
我不确定您的意思是“我需要使用 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
【讨论】:
这太过分了。正如彼得所说 - 确保您没有保留对 Popen 对象的任何引用就足够了,它将得到处理。如果你真的想等待子进程完成,你只需要使用 poll() 或 wait() - 这在问题中没有提到。 另外——如果你确实想等待子进程完成,你可以在每个子进程上使用 wait()——你不需要 poll( ) 并将它们从您的列表中删除。对每个执行 wait() 可确保您一直等到最后一个完成(如果这是您想要的)。 @ibz: 1. It is not enough to avoid keeping references to Popen objects 2. Using.wait()
might create zombies: 假设您在一个应该在一个小时内终止的进程上调用了.wait()
,并且您还有其他 1000 个子进程应该在一个小时内退出第二。繁荣。你在一秒钟内有 1000 个僵尸,直到一个小时过去了才会被收割。【参考方案7】:
像这样:s = Popen(args)
s.terminate()
time.sleep(0.5)
s.poll()
有效 僵尸进程会消失
【讨论】:
能否添加相关解释/文档?【参考方案8】:我不完全确定您所说的 no_wait()
是什么意思。你的意思是你不能阻止等待子进程完成?假设是这样,我认为这会做你想要的:
os.wait3(os.WNOHANG)
【讨论】:
【参考方案9】:最近,由于我的 python 脚本,我遇到了这个僵尸问题。实际问题主要是由于子进程被杀死而父进程不知道孩子已经死了。所以我所做的是,只是在子进程的终止信号之后添加 popen.communicate() 以便父进程知道子进程已经死了,然后内核更新子进程的pid,因为子进程已经不存在了所以现在没有僵尸形成。
PS:poll 在这里也是一个选项,因为它检查子状态并将其传达给父级。通常在子进程中,如果您不需要与标准输出和标准输入通信,最好使用 check_output 或调用。
【讨论】:
以上是关于如何使用子进程模块杀死(或避免)僵尸进程的主要内容,如果未能解决你的问题,请参考以下文章