【问题标题】:Precarious Popen Piping不稳定的 Popen 管道
【发布时间】:2014-11-05 00:11:17
【问题描述】:

我想使用subprocess.Popen来运行一个进程,要求如下。

  1. 我想在进程运行时将stdoutstderr 传回Popen 的调用者。

  2. 如果进程仍在运行,我想在timeout 秒后终止该进程。

我得出的结论是,subprocess API 中的一个缺陷意味着它不能同时满足这两个要求。考虑以下玩具程序:

chatty.py

while True:
    print 'Hi'

silence.py

while True:
    pass

caller.py

import subprocess
import time

def go(command, timeout=60):
    proc = subprocess.Popen(command, shell=True,
                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    start = time.time()
    while proc.poll() is None:
        print proc.stdout.read(1024) # <----- Line of interest
        if time.time() - start >= timeout:
            proc.kill()
            break
        else:
            time.sleep(1)

考虑上面的标记线。

  • 如果包含它,go('python silence.py') 将永远挂起 - 而不仅仅是 60 秒 - 因为 read 是一个阻塞调用,直到 1024 字节或流结束,并且两者都不会出现。

  • 如果被注释,go('python chatty.py') 将一遍又一遍地打印出'Hi',但它如何在生成时流回? proc.communicate() 阻塞直到流结束。

我会很高兴有一个解决方案将上述要求 (1) 替换为“在未发生超时的情况下,我希望在算法完成后获得 stdoutstderr。”即使这样也有问题。我的实现尝试如下。

speech.py​​

for i in xrange(0, 10000):
    print 'Hi'

caller2.py

import subprocess
import time

def go2(command, timeout=60):
    proc = subprocess.Popen(command, shell=True,
                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    start = time.time()
    while True:
        if proc.poll() is not None:
            print proc.communicate()
            break
        elif time.time() - start >= timeout:
            proc.kill()
            break
        else:
            time.sleep(1)

但即使这样仍然有问题。即使python speech.py 只运行了几秒钟,go2('python speech.py') 也需要整整 60 秒。这是因为在speech.py 中对print 'Hi' 的调用是阻塞的,直到进程被终止时调用proc.communicate()。由于proc.stdout.read 之前用silence.py 演示过这个问题,我真的不知道如何让它工作。

如何同时获得stdoutstderr 以及超时行为?

【问题讨论】:

    标签: python python-2.7 subprocess


    【解决方案1】:

    诀窍是设置一个边带定时器来终止进程。我写了一个介于健谈和沉默之间的程序:

    import time
    import sys
    
    for i in range(10,0,-1):
        print i
        time.sleep(1)
    

    然后是一个提前杀死它的程序:

    import subprocess as subp
    import threading
    import signal
    
    proc = subp.Popen(['python', 'longtime.py'], stdout=subp.PIPE, 
        stderr=subp.PIPE)
    timer = threading.Timer(3, lambda proc: proc.send_signal(signal.SIGINT),
        args=(proc,))
    timer.start()
    out, err = proc.communicate()
    timer.cancel()
    print proc.returncode
    print out
    print err
    

    然后输出:

    $ python killer.py
    1
    10
    9
    8
    
    Traceback (most recent call last):
      File "longtime.py", line 6, in <module>
        time.sleep(1)
    KeyboardInterrupt
    

    你的计时器可以做得更漂亮,比如尝试越来越糟糕的信号直到过程完成,但你明白了。

    【讨论】:

    • 看起来很有希望。明天试试这个。
    猜你喜欢
    • 2013-11-09
    • 2010-12-16
    • 2020-04-12
    • 1970-01-01
    • 2020-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多