【问题标题】:Python, Popen and select - waiting for a process to terminate or a timeoutPython,Popen 和 select - 等待进程终止或超时
【发布时间】:2010-09-25 04:05:09
【问题描述】:

我使用以下方式运行子进程:

  p = subprocess.Popen("subprocess", 
                       stdout=subprocess.PIPE, 
                       stderr=subprocess.PIPE, 
                       stdin=subprocess.PIPE)

此子进程可以在 stderr 上出现错误时立即退出,或者继续运行。我想检测其中任何一种情况 - 后者需要等待几秒钟。

我试过了:

  SECONDS_TO_WAIT = 10
  select.select([], 
                [p.stdout, p.stderr], 
                [p.stdout, p.stderr],
                SECONDS_TO_WAIT)

但它只是返回:

  ([],[],[])

在任何一种情况下。我能做什么?

【问题讨论】:

  • 注意:如果子进程产生足够的输出,它可能会死锁。如果使用 PIPE,则需要消耗 stdout/stderr

标签: python select timeout subprocess popen


【解决方案1】:

您是否尝试过使用 Popen.Poll() 方法。你可以这样做:

p = subprocess.Popen("subprocess", 
                   stdout=subprocess.PIPE, 
                   stderr=subprocess.PIPE, 
                   stdin=subprocess.PIPE)

time.sleep(SECONDS_TO_WAIT)
retcode = p.poll()
if retcode is not None:
   # process has terminated

这将导致您始终等待 10 秒,但如果失败案例很少,这将在所有成功案例中摊销。


编辑:

怎么样:

t_nought = time.time()
seconds_passed = 0

while(p.poll() is not None and seconds_passed < 10):
    seconds_passed = time.time() - t_nought

if seconds_passed >= 10:
   #TIMED OUT

这有一个忙着等待的丑陋,但我认为它完成了你想要的。

另外再看一下选择调用文档,我想你可能想按如下方式进行更改:

SECONDS_TO_WAIT = 10
  select.select([p.stderr], 
                [], 
                [p.stdout, p.stderr],
                SECONDS_TO_WAIT)

由于您通常希望从 stderr 中读取数据,因此您想知道它何时有可读取的内容(即失败案例)。

我希望这会有所帮助。

【讨论】:

  • 感谢您的回复,但不幸的是我不能使用这种方法,因为失败的情况是最常见的。该程序预计会出现大约 600 次失败(每次调整参数),然后最后一次成功。目前,我正在使用commands.getstatusoutput,但成功后它会挂起。
  • 编辑了我的答案以考虑您的具体用例。
  • if retcode: 如果子进程成功完成则失败,即对于retcode == 0;你可以使用if retcode is not None:
  • @J.F.Sebastian: 好收获!已编辑以解决该问题。
【解决方案2】:

这就是我想出的。在您需要并且不需要在该进程上超时时工作,但有一个半忙循环。

def runCmd(cmd, timeout=None):
    '''
    Will execute a command, read the output and return it back.

    @param cmd: command to execute
    @param timeout: process timeout in seconds
    @return: a tuple of three: first stdout, then stderr, then exit code
    @raise OSError: on missing command or if a timeout was reached
    '''

    ph_out = None # process output
    ph_err = None # stderr
    ph_ret = None # return code

    p = subprocess.Popen(cmd, shell=True,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE)
    # if timeout is not set wait for process to complete
    if not timeout:
        ph_ret = p.wait()
    else:
        fin_time = time.time() + timeout
        while p.poll() == None and fin_time > time.time():
            time.sleep(1)

        # if timeout reached, raise an exception
        if fin_time < time.time():

            # starting 2.6 subprocess has a kill() method which is preferable
            # p.kill()
            os.kill(p.pid, signal.SIGKILL)
            raise OSError("Process timeout has been reached")

        ph_ret = p.returncode


    ph_out, ph_err = p.communicate()

    return (ph_out, ph_err, ph_ret)

【讨论】:

    【解决方案3】:

    这是一个很好的例子:

    from threading import Timer
    from subprocess import Popen, PIPE
    
    proc = Popen("ping 127.0.0.1", shell=True)
    t = Timer(60, proc.kill)
    t.start()
    proc.wait()
    

    【讨论】:

      【解决方案4】:

      使用 select 和 sleep 并没有多大意义。 select (或任何内核轮询机制)本质上对异步编程很有用,但您的示例是同步的。所以要么重写你的代码以使用正常的阻塞方式,要么考虑使用 Twisted:

      from twisted.internet.utils import getProcessOutputAndValue
      from twisted.internet import reactor    
      
      def stop(r):
          reactor.stop()
      def eb(reason):
          reason.printTraceback()
      def cb(result):
          stdout, stderr, exitcode = result
          # do something
      getProcessOutputAndValue('/bin/someproc', []
          ).addCallback(cb).addErrback(eb).addBoth(stop)
      reactor.run()
      

      顺便说一句,通过编写自己的 ProcessProtocol,使用 Twisted 有一种更安全的方法:

      http://twistedmatrix.com/projects/core/documentation/howto/process.html

      【讨论】:

      • 使用本示例中的代码和使用完整的 ProcessProtocol 有什么区别?
      【解决方案5】:

      Python 3.3

      import subprocess as sp
      
      try:
          sp.check_call(["/subprocess"], timeout=10,
                        stdin=sp.DEVNULL, stdout=sp.DEVNULL, stderr=sp.DEVNULL)
      except sp.TimeoutError:
          # timeout (the subprocess is killed at this point)
      except sp.CalledProcessError:
          # subprocess failed before timeout
      else:
          # subprocess ended successfully before timeout
      

      TimeoutExpired docs

      【讨论】:

        【解决方案6】:

        如果如您在上面的 cmets 中所说,您只是每次调整输出并重新运行命令,是否会像以下那样工作?

        from threading import Timer
        import subprocess
        
        WAIT_TIME = 10.0
        
        def check_cmd(cmd):
            p = subprocess.Popen(cmd,
                stdout=subprocess.PIPE, 
                    stderr=subprocess.PIPE)
            def _check():
                if p.poll()!=0:
                    print cmd+" did not quit within the given time period."
        
            # check whether the given process has exited WAIT_TIME
            # seconds from now
            Timer(WAIT_TIME, _check).start()
        
        check_cmd('echo')
        check_cmd('python')
        

        上面的代码在运行时会输出:

        python did not quit within the given time period.
        

        我能想到的上述代码的唯一缺点是在您继续运行 check_cmd 时可能会出现重叠的进程。

        【讨论】:

          【解决方案7】:

          这是对 Evan 答案的解释,但它考虑了以下几点:

          1. 显式取消 Timer 对象:如果 Timer 间隔很长并且进程将按“自己的意愿”退出,这可能会挂起您的脚本 :(
          2. 在 Timer 方法中有一个内在的竞争(计时器试图在进程死亡之后杀死进程,这在 Windows 上会引发异常)。

              DEVNULL = open(os.devnull, "wb")
              process = Popen("c:/myExe.exe", stdout=DEVNULL) # no need for stdout
            
              def kill_process():
              """ Kill process helper"""
              try:
                 process.kill()
               except OSError:
                 pass  # Swallow the error
            
              timer = Timer(timeout_in_sec, kill_process)
              timer.start()
            
              process.wait()
              timer.cancel()
            

          【讨论】:

            猜你喜欢
            • 2015-06-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-12-27
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多