如何在杀死由Python subprocess Popen启动的进程时关闭stdout管道?

9 投票
3 回答
4875 浏览
提问于 2025-04-15 15:23

我在想,如果在不同线程中启动的子进程被杀掉时,是否可以关闭它的通信管道。如果我不调用 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 脚本中启动的子进程。

这个问题和解决方法可以在这里找到:

可以使用 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

撰写回答