如何在杀死由Python subprocess Popen启动的进程时关闭stdout管道?
我在想,如果在不同线程中启动的子进程被杀掉时,是否可以关闭它的通信管道。如果我不调用 communicate() 方法,那么 kill() 方法就会按预期工作,在一秒钟后终止进程,而不是五秒。
我找到了一些关于类似问题的讨论,在这里,但没有得到真正的答案。我猜我要么需要能够关闭管道,要么需要明确地杀掉子进程(在这个例子中是“sleep”),然后再杀掉它来解除管道的阻塞。
我也试着在 StackOverflow 上找到答案,但只找到了这个、这个和这个,就我所知,它们并没有直接解决这个问题(?)。
我想做的事情是能够在第二个线程中运行一个命令,并获取它的所有输出,但当我想要时能够立即杀掉它。我可以通过一个文件来实现,监控那个文件的变化,但我觉得应该有更好的方法来做到这一点?
import subprocess, time
from threading import Thread
process = None
def executeCommand(command, runCommand):
Thread(target=runCommand, args=(command,)).start()
def runCommand(command):
global process
args = command.strip().split()
process = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE)
for line in process.communicate():
if line:
print "process:", line,
if __name__ == '__main__':
executeCommand("./ascript.sh", runCommand)
time.sleep(1)
process.kill()
这是脚本:
#!/bin/bash
echo "sleeping five"
sleep 5
echo "slept five"
输出
$ time python poc.py
process: sleeping five
real 0m5.053s
user 0m0.044s
sys 0m0.000s
3 个回答
-1
看起来你可能遇到了Python的一个问题,就是它的并发处理比较粗糙。你可以把你的脚本改成这样:
#!/bin/bash
echo "sleeping five"
sleep 5
echo "sleeping five again"
sleep 5
echo "slept five"
然后输出就变成了:
process: sleeping five
real 0m5.134s
user 0m0.000s
sys 0m0.010s
如果整个脚本都运行完,时间会是10秒。所以看起来Python的控制线程实际上是在bash脚本休眠之后才开始运行的。同样,如果你把脚本改成这样:
#!/bin/bash
echo "sleeping five"
sleep 1
sleep 1
sleep 1
sleep 1
sleep 1
echo "slept five"
那么输出就变成了:
process: sleeping five
real 0m1.150s
user 0m0.010s
sys 0m0.020s
总之,你的代码逻辑上是正确的。:)
0
我觉得最简单的方法就是在主线程里设置一个“停止”标志,然后在运行脚本的线程中,在进行通信之前检查这个标志。如果这个标志是True
,就结束脚本的运行。
6
我觉得问题在于,process.kill() 只会结束直接的子进程(也就是 bash),而不会结束 bash 脚本中启动的子进程。
这个问题和解决方法可以在这里找到:
- http://www.doughellmann.com/PyMOTW/subprocess/#process-groups-sessions
- 如何结束一个通过 shell=True 启动的 Python 子进程
可以使用 Popen(..., preexec_fn=os.setsid) 来创建一个进程组,然后用 os.pgkill 来结束整个进程组。例如:
import os
import signal
import subprocess
import time
from threading import Thread
process = None
def executeCommand(command, runCommand):
Thread(target=runCommand, args=(command,)).start()
def runCommand(command):
global process
args = command.strip().split()
process = subprocess.Popen(
args, shell=False, stdout=subprocess.PIPE, preexec_fn=os.setsid)
for line in process.communicate():
if line:
print "process:", line,
if __name__ == '__main__':
executeCommand("./ascript.sh", runCommand)
time.sleep(1)
os.killpg(process.pid, signal.SIGKILL)
$ time python poc.py
process: sleeping five
real 0m1.051s
user 0m0.032s
sys 0m0.020s