岌岌可危的Popen管道

时间:2014-11-05 00:11:17

标签: python python-2.7 subprocess

我想使用subprocess.Popen来运行一个具有以下要求的流程。

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

  2. 我想在timeout秒之后杀死该进程,如果它仍然在运行。

  3. 我得出结论,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()阻止直到流结束。

    我很满意用以下#34替换上述要求(1)的解决方案;如果没有发生超时,我希望算法获得stdoutstderr饰面&#34。即使这样也存在问题。我的实施尝试如下。

    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秒。这是因为print 'Hi'中对speech.py的调用阻塞,直到进程被终止时调用proc.communicate()。由于proc.stdout.read之前已经证明了问题silence.py,因此我真的不知道如何使其发挥作用。

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

1 个答案:

答案 0 :(得分: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

你的计时器可以变得更漂亮,就像尝试越来越糟糕的信号直到整个过程完成,但你明白了。