【问题标题】:How do I close the stdout-pipe when killing a process started with python subprocess Popen?终止使用 python 子进程 Popen 启动的进程时,如何关闭标准输出管道?
【发布时间】:2010-12-10 02:28:23
【问题描述】:

我想知道在杀死在不同线程中启动的子进程时是否可以关闭通信管道。如果我不调用communicate(),那么kill() 将按预期工作,在一秒而不是五秒后终止进程。

我发现了一个关于类似问题的讨论here,但我没有得到真正的答案。我假设我必须能够关闭管道或显式终止子子进程(在示例中为“睡眠”)并终止它以解除管道阻塞。

我也尝试在SO上找到她的答案,但我只找到了thisthisthis,据我所知,它们并没有直接解决这个问题(?)。

所以我想做的事情是能够在第二个线程中运行命令并获得其所有输出,但能够在我需要时立即杀死它。我可以通过一个文件并跟踪它或类似的东西,但我认为应该有更好的方法来做到这一点?

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

【问题讨论】:

  • process.kill() 被调用时,./ascript.sh 会死掉,但里面的sleep 5 不会。不过有趣的是,./ascript.sh 死后 Python 不会立即返回。

标签: python pipe subprocess stdout kill


【解决方案1】:

我认为问题在于 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

【讨论】:

    【解决方案2】:

    在我看来,做到这一点并避开多线程问题的最简单方法是在主线程中设置一个终止标志,并在通信之前在脚本运行线程中检查它,在何时终止脚本标志是True

    【讨论】:

    • 实际上,阻塞管道的问题比多线程问题更多。我需要一个不同的线程,以便我可以从某个地方关闭它。如果我从 shell 中“杀死”,它会杀死脚本及其子进程。 Python 的 kill 未能做到这一点,因为它似乎希望管道关闭。
    【解决方案3】:

    看起来您可能是 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
    

    简而言之,您的代码按照逻辑实现方式运行。 :)

    【讨论】:

    • 示例中的 sleep 只是脚本中任何冗长操作的占位符。问题是,如何让管道关闭以使代码不会挂在 communcate() 上?或者,我如何找出要杀死哪些“子子进程”来释放块?
    • 你的意思是全局解释器锁在努力工作吗? docs.python.org/c-api/…
    • 我不知道问题是 GIL 还是与操作系统调度的某些交互。我只知道 python 没有等待 bash 脚本终止,正如我的示例所证明的那样。您可以尝试在替代 python 实现上运行它。如果不是一个实用的解决方案,那将是一个很好的诊断:python.org/dev/implementations
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-03-11
    • 2023-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多