如何在Python中干净地终止子进程

8
我们正在使用一个Python进程来管理长时间运行的Python子进程。有时需要杀死子进程。kill命令并不能完全杀死进程,只会使它变为僵尸进程。
运行以下脚本可以演示此行为。
import subprocess
p = subprocess.Popen(['sleep', '400'], stdout=subprocess.PIPE, shell=False)

或者

p = subprocess.Popen('sleep 400', stdout=subprocess.PIPE, shell=True)

创建一个子进程。

p.terminate() 
p.kill()

这不会对进程产生影响。通过ps aux | grep sleep进行演示。

$ ps aux| grep 'sleep'
User       8062  0.0  0.0   7292   764 pts/7    S    14:53   0:00 sleep 400

进程并未被终止或变为死进程。使用subprocess.call()函数并将'kill'pid作为参数,即可发出kill命令。
subprocess.call(['kill', str(p.pid)])

这会终止进程,但该进程已不可用。

$ ps aux | grep 'sleep'
User       8062  0.0  0.0      0     0 pts/7    Z+   14:51   0:00 [sleep] <defunct>

如果队列运行时间足够长,它是否会达到最大进程数,还是最终会收割死亡进程并正常工作?

如果答案是前者,那么如何在Python中处理死亡进程而不杀死父进程?

有没有更好的杀死进程的方法?


1
kill 命令只是向进程发送一个“信号”,要求其终止工作。但如果你使用 -9 选项来 kill 进程,它将被操作系统强制终止。然而,这被认为是一种不好的结束进程的方式。 - Willem Van Onsem
kill -9 做的事情和 subprocess.kill() 函数相同。无论我是尝试使用 p.terminate() 干净地终止它还是使用 p.kill() 杀死它,它最终都会成为僵尸进程。 - user7495615
我使用Popen.(['ffmpeg' etc...])将本地主机的UDP数据保存到一个mp3文件中。当通过IDE结束执行时,一切正常,但当我尝试通过代码本身使用Popen.terminate()Popen.wait()来结束它时,文件大小为零。 - bomben
2个回答

23

这里有两个主要问题:

第一个问题:如果你正在使用 shell=True,那么你杀死的是运行进程的 shell,而不是进程本身。由于其父进程被杀死,子进程变成僵尸进程 / 不会立即被杀死。

在你的情况下,你正在使用非内置的 sleep 命令,所以可以去掉 shell=TruePopen 将返回实际的进程ID: p.terminate() 就能起作用了。

大多数情况下,即使需要额外编写 Python 代码(将两个命令连接起来、重定向输入/输出等),也应该避免使用 shell=True(所有这些情况都可以通过一个或多个不带 shell=TruePopen 很好地处理)。

另外一个问题是,在使用上述方法修复后,如果进程仍然是僵尸进程,则可以调用 p.wait()(参见问题)。似乎仅仅调用 terminate 是不够的,Popen 对象需要被垃圾回收。


2
谢谢,p.terminate() 调用后加上 p.wait() 解决了这个问题! - user7495615
仅供参考,您可以尝试使用 shell=True 来查看它是否仍然有效?或者您是否会得到一个僵尸进程? - Jean-François Fabre
使用shell=True参数时,ps aux | grep sleep的行为如下:User 9689 0.0 0.0 4508 796 pts/7 S+ 16:28 0:00 /bin/sh -c sleep 100 User 9690 0.0 0.0 7292 648 pts/7 S+ 16:28 0:00 sleep 100然后执行p.kill()p.terminate()命令。$ ps aux| grep 'sleep' User 9690 0.0 0.0 7292 648 pts/7 S+ 16:28 0:00 sleep 100 - user7495615
不,我认为那样做行不通。在发出p.terminate()命令后仍然有一个sleep进程在运行,kill命令已经杀死了shell命令(我想!) - user7495615
还有其他问题吗?我使用Popen.(['ffmpeg'等...])将来自本地主机的UDP数据保存到mp3文件中。当我通过IDE结束执行时,一切正常,但是当我尝试通过代码本身结束它时,使用Popen.terminate()Popen.wait(),文件大小为零。 - bomben
似乎这应该是另一个问题。 - Jean-François Fabre

8

在终止子进程后,您应该调用 p.wait() - 以清除进程表。这将删除僵尸进程(处于defunct状态)。


2
谢谢,是的,这确实解决了问题。 - user7495615
1
如果 shell=False,那么这将解决问题(所以+1)。 - Jean-François Fabre
需要设置shell=False,这样Python不会生成新的shell,并直接调用clone/exec - VenkatC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接