【问题标题】:Using module 'subprocess' with timeout使用带有超时的模块“子进程”
【发布时间】:2010-11-14 13:13:35
【问题描述】:

这是运行返回其stdout 数据的任意命令或在非零退出代码上引发异常的 Python 代码:

proc = subprocess.Popen(
    cmd,
    stderr=subprocess.STDOUT,  # Merge stdout and stderr
    stdout=subprocess.PIPE,
    shell=True)

communicate用于等待进程退出:

stdoutdata, stderrdata = proc.communicate()

subprocess 模块不支持超时——能够终止运行超过 X 秒的进程——因此,communicate 可能需要永远运行。

在要在 Windows 和 Linux 上运行的 Python 程序中实现超时的最简单方法是什么?

【问题讨论】:

标签: python multithreading timeout subprocess


【解决方案1】:

如果你在 Unix 上,

import signal
  ...
class Alarm(Exception):
    pass

def alarm_handler(signum, frame):
    raise Alarm

signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(5*60)  # 5 minutes
try:
    stdoutdata, stderrdata = proc.communicate()
    signal.alarm(0)  # reset the alarm
except Alarm:
    print "Oops, taking too long!"
    # whatever else

【讨论】:

  • 好吧,我对至少在 win/linux/mac 上有效的跨平台解决方案感兴趣。
  • 我喜欢这种基于 unix 的方法。理想情况下,可以将其与特定于 Windows 的方法(使用 CreateProcess 和 Jobs)结合起来。但就目前而言,下面的解决方案简单、容易且有效。
  • 我添加了便携解决方案,看我的回答
  • 这个解决方案可以工作 only_if signal.signal(signal.SIGALARM, alarm_handler) 从主线程调用。请参阅信号文档
  • 不幸的是,在 Apache 模块(如 mod_python、mod_perl 或 mod_php)的上下文中运行(在 linux 上)时,我发现不允许使用信号和警报(可能是因为它们干扰 Apache 自己的 IPC 逻辑)。因此,为了实现使命令超时的目标,我不得不编写“父循环”来启动子进程,然后坐在“睡眠”循环中观察时钟(并且可能还监视子进程的输出)。
【解决方案2】:

我已经在 Windows、Linux 和 Mac 上成功使用了killableprocess。如果您使用的是 Cygwin Python,则需要 OSAF's version of killableprocess,否则本机 Windows 进程不会被杀死。

【讨论】:

  • 看起来 killableprocess 没有为 Popen.communicate() 调用添加超时。
【解决方案3】:

这是 Alex Martelli 的解决方案,它是一个带有适当进程终止的模块。其他方法不起作用,因为它们不使用 proc.communicate()。因此,如果您有一个产生大量输出的进程,它将填充其输出缓冲区,然后阻塞,直到您从中读取一些内容。

from os import kill
from signal import alarm, signal, SIGALRM, SIGKILL
from subprocess import PIPE, Popen

def run(args, cwd = None, shell = False, kill_tree = True, timeout = -1, env = None):
    '''
    Run a command with a timeout after which it will be forcibly
    killed.
    '''
    class Alarm(Exception):
        pass
    def alarm_handler(signum, frame):
        raise Alarm
    p = Popen(args, shell = shell, cwd = cwd, stdout = PIPE, stderr = PIPE, env = env)
    if timeout != -1:
        signal(SIGALRM, alarm_handler)
        alarm(timeout)
    try:
        stdout, stderr = p.communicate()
        if timeout != -1:
            alarm(0)
    except Alarm:
        pids = [p.pid]
        if kill_tree:
            pids.extend(get_process_children(p.pid))
        for pid in pids:
            # process might have died before getting to this line
            # so wrap to avoid OSError: no such process
            try: 
                kill(pid, SIGKILL)
            except OSError:
                pass
        return -9, '', ''
    return p.returncode, stdout, stderr

def get_process_children(pid):
    p = Popen('ps --no-headers -o pid --ppid %d' % pid, shell = True,
              stdout = PIPE, stderr = PIPE)
    stdout, stderr = p.communicate()
    return [int(p) for p in stdout.split()]

if __name__ == '__main__':
    print run('find /', shell = True, timeout = 3)
    print run('find', shell = True)

【讨论】:

  • 这在windows上是行不通的,而且函数的顺序是颠倒的。
  • 这有时会导致异常,当另一个处理程序在 SIGALARM 上注册自己并在此进程“杀死”之前终止进程,添加了解决方法。顺便说一句,很棒的食谱!到目前为止,我已经使用它启动了 50,000 个有问题的进程,而没有冻结或崩溃处理包装器。
  • 如何修改它以在线程应用程序中运行?我正在尝试从工作线程中使用它并获取ValueError: signal only works in main thread
  • @Yaroslav Bulatov 感谢您提供信息。您为处理提到的问题而添加的解决方法是什么?
  • 刚刚添加了“try;catch”块,它在代码里面。顺便说一句,从长远来看,这给我带来了问题,因为您只能设置一个 SIGALARM 处理程序,而其他进程可以重置它。这里给出了一个解决方案——stackoverflow.com/questions/6553423/…
【解决方案4】:

另一种选择是写入临时文件以防止标准输出阻塞,而不是需要使用communicate() 进行轮询。这对我有用,而其他答案却没有;例如在 Windows 上。

    outFile =  tempfile.SpooledTemporaryFile() 
    errFile =   tempfile.SpooledTemporaryFile() 
    proc = subprocess.Popen(args, stderr=errFile, stdout=outFile, universal_newlines=False)
    wait_remaining_sec = timeout

    while proc.poll() is None and wait_remaining_sec > 0:
        time.sleep(1)
        wait_remaining_sec -= 1

    if wait_remaining_sec <= 0:
        killProc(proc.pid)
        raise ProcessIncompleteError(proc, timeout)

    # read temp streams from start
    outFile.seek(0);
    errFile.seek(0);
    out = outFile.read()
    err = errFile.read()
    outFile.close()
    errFile.close()

【讨论】:

  • 似乎不完整 - 什么是临时文件?
  • 在“Popen”调用中包含“import tempfile”、“import time”和“shell=True”(注意“shell=True”)!
【解决方案5】:

我不太了解底层细节;但是,鉴于在 python 2.6 API 提供了等待线程和 终止进程,如何在单独的进程中运行该进程 线程?

import subprocess, threading

class Command(object):
    def __init__(self, cmd):
        self.cmd = cmd
        self.process = None

    def run(self, timeout):
        def target():
            print 'Thread started'
            self.process = subprocess.Popen(self.cmd, shell=True)
            self.process.communicate()
            print 'Thread finished'

        thread = threading.Thread(target=target)
        thread.start()

        thread.join(timeout)
        if thread.is_alive():
            print 'Terminating process'
            self.process.terminate()
            thread.join()
        print self.process.returncode

command = Command("echo 'Process started'; sleep 2; echo 'Process finished'")
command.run(timeout=3)
command.run(timeout=1)

这个sn-p在我机器上的输出是:

Thread started
Process started
Process finished
Thread finished
0
Thread started
Process started
Terminating process
Thread finished
-15

可以看出,在第一次执行中,进程 正确完成(返回代码 0),而在第二个 进程已终止(返回码 -15)。

我没有在 Windows 中测试过;但是,除了更新示例 命令,我认为它应该可以工作,因为我还没有找到 记录任何说 thread.join 或 process.terminate 不支持。

【讨论】:

  • +1 独立于平台。我已经在 linux 和 windows 7(cygwin 和普通 windows python)上运行了它——在所有三种情况下都按预期工作。
  • 我已经稍微修改了您的代码,以便能够通过本机 Popen kwargs 并将其放在要点上。现在可以多用途使用了; gist.github.com/1306188
  • 对于遇到@redice 问题的任何人,this question 可能会有所帮助。简而言之,如果您使用 shell=True,shell 将成为被杀死的子进程,并且它的命令(子进程的子进程)继续存在。
  • 此答案不提供与原始答案相同的功能,因为它不返回标准输出。
  • thread.is_alive 可能导致竞争条件。见ostricher.com/2015/01/python-subprocess-with-timeout
【解决方案6】:

我已经实现了我可以从其中一些中收集到的东西。这适用于 Windows,由于这是一个社区 wiki,我想我也会分享我的代码:

class Command(threading.Thread):
    def __init__(self, cmd, outFile, errFile, timeout):
        threading.Thread.__init__(self)
        self.cmd = cmd
        self.process = None
        self.outFile = outFile
        self.errFile = errFile
        self.timed_out = False
        self.timeout = timeout

    def run(self):
        self.process = subprocess.Popen(self.cmd, stdout = self.outFile, \
            stderr = self.errFile)

        while (self.process.poll() is None and self.timeout > 0):
            time.sleep(1)
            self.timeout -= 1

        if not self.timeout > 0:
            self.process.terminate()
            self.timed_out = True
        else:
            self.timed_out = False

然后从另一个类或文件:

        outFile =  tempfile.SpooledTemporaryFile()
        errFile =   tempfile.SpooledTemporaryFile()

        executor = command.Command(c, outFile, errFile, timeout)
        executor.daemon = True
        executor.start()

        executor.join()
        if executor.timed_out:
            out = 'timed out'
        else:
            outFile.seek(0)
            errFile.seek(0)
            out = outFile.read()
            err = errFile.read()

        outFile.close()
        errFile.close()

【讨论】:

  • 其实这可能行不通。 terminate() 函数将线程标记为已终止,但实际上并未终止线程!我可以在 *nix 中验证这一点,但我没有要测试的 Windows 计算机。
【解决方案7】:

我将jcollado 的线程解决方案添加到我的Python 模块easyprocess

安装:

pip install easyprocess

例子:

from easyprocess import Proc

# shell is not supported!
stdout=Proc('ping localhost').call(timeout=1.5).stdout
print stdout

【讨论】:

【解决方案8】:

虽然我没有深入研究过它,但我在 ActiveState 发现的这个 decorator 似乎对这类事情非常有用。除了subprocess.Popen(..., close_fds=True),至少我已经准备好在 Python 中编写 shell 脚本了。

【讨论】:

  • 这个装饰器使用了signal.alarm,这在Windows上是不可用的。
【解决方案9】:

我使用的解决方案是在 shell 命令前加上 timelimit。如果命令花费的时间太长,timelimit 将停止它,Popen 将有一个由 timelimit 设置的返回码。如果 > 128,则表示 timelimit 杀死了进程。

另见python subprocess with timeout and large output (>64K)

【讨论】:

【解决方案10】:

jcollado 的答案可以使用threading.Timer 类进行简化:

import shlex
from subprocess import Popen, PIPE
from threading import Timer

def run(cmd, timeout_sec):
    proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE)
    timer = Timer(timeout_sec, proc.kill)
    try:
        timer.start()
        stdout, stderr = proc.communicate()
    finally:
        timer.cancel()

# Examples: both take 1 second
run("sleep 1", 5)  # process ends normally at 1 second
run("sleep 5", 1)  # timeout happens at 1 second

【讨论】:

  • +1 用于简单的便携式解决方案。你不需要lambdat = Timer(timeout, proc.kill)
  • +1 这应该是公认的答案,因为它不需要更改启动流程的方式。
  • 为什么需要 lambda?绑定方法 p.kill 不能在没有 lambda 的情况下使用吗?
  • // , 你愿意提供一个使用这个的例子吗?
  • @tuk timer.isAlive() before timer.cancel() 表示正常结束
【解决方案11】:

一旦你了解了 *unix 中的全进程运行机制,你会很容易找到更简单的解决方案:

考虑这个简单的例子,如何使用 select.select() 制作可超时的communicate() 方法(现在在 *nix 上几乎随处可用)。这也可以用 epoll/poll/kqueue 编写,但是 select.select() 变体可能是一个很好的例子。 select.select() 的主要限制(速度和 1024 max fds)不适用于您的任务。

这在 *nix 下工作,不创建线程,不使用信号,可以从任何线程(不仅是主线程)启动,并且足够快以从我的机器上的 stdout 读取 250mb/s 的数据(i5 2.3ghz )。

在通信结束时加入 stdout/stderr 时出现问题。如果您有大量程序输出,这可能会导致大量内存使用。但是您可以多次调用communicate(),但超时时间更短。

class Popen(subprocess.Popen):
    def communicate(self, input=None, timeout=None):
        if timeout is None:
            return subprocess.Popen.communicate(self, input)

        if self.stdin:
            # Flush stdio buffer, this might block if user
            # has been writing to .stdin in an uncontrolled
            # fashion.
            self.stdin.flush()
            if not input:
                self.stdin.close()

        read_set, write_set = [], []
        stdout = stderr = None

        if self.stdin and input:
            write_set.append(self.stdin)
        if self.stdout:
            read_set.append(self.stdout)
            stdout = []
        if self.stderr:
            read_set.append(self.stderr)
            stderr = []

        input_offset = 0
        deadline = time.time() + timeout

        while read_set or write_set:
            try:
                rlist, wlist, xlist = select.select(read_set, write_set, [], max(0, deadline - time.time()))
            except select.error as ex:
                if ex.args[0] == errno.EINTR:
                    continue
                raise

            if not (rlist or wlist):
                # Just break if timeout
                # Since we do not close stdout/stderr/stdin, we can call
                # communicate() several times reading data by smaller pieces.
                break

            if self.stdin in wlist:
                chunk = input[input_offset:input_offset + subprocess._PIPE_BUF]
                try:
                    bytes_written = os.write(self.stdin.fileno(), chunk)
                except OSError as ex:
                    if ex.errno == errno.EPIPE:
                        self.stdin.close()
                        write_set.remove(self.stdin)
                    else:
                        raise
                else:
                    input_offset += bytes_written
                    if input_offset >= len(input):
                        self.stdin.close()
                        write_set.remove(self.stdin)

            # Read stdout / stderr by 1024 bytes
            for fn, tgt in (
                (self.stdout, stdout),
                (self.stderr, stderr),
            ):
                if fn in rlist:
                    data = os.read(fn.fileno(), 1024)
                    if data == '':
                        fn.close()
                        read_set.remove(fn)
                    tgt.append(data)

        if stdout is not None:
            stdout = ''.join(stdout)
        if stderr is not None:
            stderr = ''.join(stderr)

        return (stdout, stderr)

【讨论】:

  • 这只解决了 Unix 问题的一半。
【解决方案12】:

我已修改 susudio 答案。现在函数返回: (returncode, stdout, stderr, timeout) - stdoutstderr 被解码为 utf-8 字符串

def kill_proc(proc, timeout):
  timeout["value"] = True
  proc.kill()

def run(cmd, timeout_sec):
  proc = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  timeout = {"value": False}
  timer = Timer(timeout_sec, kill_proc, [proc, timeout])
  timer.start()
  stdout, stderr = proc.communicate()
  timer.cancel()
  return proc.returncode, stdout.decode("utf-8"), stderr.decode("utf-8"), timeout["value"]

【讨论】:

    【解决方案13】:

    很遗憾,我受到雇主披露源代码的严格政策的约束,因此我无法提供实际代码。但就我的口味而言,最好的解决方案是创建一个覆盖Popen.wait() 的子类来轮询而不是无限期地等待,并创建一个Popen.__init__ 来接受超时参数。一旦你这样做了,所有其他Popen 方法(调用wait)将按预期工作,包括communicate

    【讨论】:

      【解决方案14】:

      在 Python 3.3+ 中:

      from subprocess import STDOUT, check_output
      
      output = check_output(cmd, stderr=STDOUT, timeout=seconds)
      

      output 是一个字节字符串,包含命令的合并标准输出、标准错误数据。

      check_output 引发CalledProcessError 在问题文本中指定的非零退出状态与proc.communicate() 方法不同。

      我删除了shell=True,因为它经常被不必要地使用。如果cmd 确实需要它,您可以随时添加它。如果您添加shell=True,即如果子进程产生自己的后代; check_output() 可以比超时指示的更晚返回,请参阅Subprocess timeout failure

      超时功能在 Python 2.x 上通过 3.2+ 子进程模块的subprocess32 backport 可用。

      【讨论】:

      • 确实,子进程超时支持存在于我为在 Python 2 上使用而维护的 subprocess32 反向端口中。pypi.python.org/pypi/subprocess32
      • @gps Sridhar 要求提供跨平台解决方案,而您的 backport 仅支持 POSIX:当我尝试时,MSVC 抱怨(预期)缺少 unistd.h :)
      • 如果不需要输出,直接使用subprocess.call即可。
      • Python3.5 起,使用 subprocess.run() 和 capture_output=True 并使用 encoding 参数获取有用的输出。
      • @MKesper: 1- check_output() 是获取输出的首选方式(它直接返回输出,不会忽略错误,它永远可用)。 2- run() 更灵活,但 run() 默认忽略错误,需要额外的步骤才能获得输出 3- check_output() is implemented in terms of run() 因此它接受大多数相同的参数。 4- nit: capture_output 从 3.7 开始可用,而不是 3.5
      【解决方案15】:

      只是想写一些更简单的东西。

      #!/usr/bin/python
      
      from subprocess import Popen, PIPE
      import datetime
      import time 
      
      popen = Popen(["/bin/sleep", "10"]);
      pid = popen.pid
      sttime = time.time();
      waittime =  3
      
      print "Start time %s"%(sttime)
      
      while True:
          popen.poll();
          time.sleep(1)
          rcode = popen.returncode
          now = time.time();
          if [ rcode is None ]  and  [ now > (sttime + waittime) ] :
              print "Killing it now"
              popen.kill()
      

      【讨论】:

      • time.sleep(1) 是一个非常糟糕的主意 - 假设您想要运行许多需要大约 0.002 秒的命令。您应该等待 poll() (请参阅 select,对于推荐的 Linux epol :)
      【解决方案16】:

      这是我的解决方案,我使用的是线程和事件:

      import subprocess
      from threading import Thread, Event
      
      def kill_on_timeout(done, timeout, proc):
          if not done.wait(timeout):
              proc.kill()
      
      def exec_command(command, timeout):
      
          done = Event()
          proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
      
          watcher = Thread(target=kill_on_timeout, args=(done, timeout, proc))
          watcher.daemon = True
          watcher.start()
      
          data, stderr = proc.communicate()
          done.set()
      
          return data, stderr, proc.returncode
      

      在行动:

      In [2]: exec_command(['sleep', '10'], 5)
      Out[2]: ('', '', -9)
      
      In [3]: exec_command(['sleep', '10'], 11)
      Out[3]: ('', '', 0)
      

      【讨论】:

        【解决方案17】:

        timeout is now supported by call()communicate() 在子进程模块中(从 Python3.3 开始):

        import subprocess
        
        subprocess.call("command", timeout=20, shell=True)
        

        这将调用命令并引发异常

        subprocess.TimeoutExpired
        

        如果命令在 20 秒后没有完成。

        然后您可以处理异常以继续您的代码,例如:

        try:
            subprocess.call("command", timeout=20, shell=True)
        except subprocess.TimeoutExpired:
            # insert code here
        

        希望这会有所帮助。

        【讨论】:

        【解决方案18】:

        很惊讶没有人提到使用timeout

        timeout 5 ping -c 3 somehost

        这显然不适用于每个用例,但如果您处理的是一个简单的脚本,这很难被击败。

        Mac 用户也可以通过homebrew 在 coreutils 中作为 gtimeout 使用。

        【讨论】:

        • 你的意思是:proc = subprocess.Popen(['/usr/bin/timeout', str(timeout)] + cmd, ...)。 OP 要求在 Windows 上是否有 timeout 命令?
        • 在 Windows 中,可以使用像 git bash 这样的应用程序,它允许在 Windows 中使用 bash 实用程序。
        • @KaushikAcharya 即使你使用 git bash,当 python 调用 subprocess 时它也会在 Windows 上运行,因此这种绕过将不起作用。
        【解决方案19】:

        https://pypi.python.org/pypi/python-subprocess2 为子流程模块提供扩展,允许您等待一段时间,否则终止。

        所以,要等待最多 10 秒让进程终止,否则终止:

        pipe  = subprocess.Popen('...')
        
        timeout =  10
        
        results = pipe.waitOrTerminate(timeout)
        

        这与 windows 和 unix 都兼容。 “results”是一个字典,它包含“returnCode”,它是应用程序的返回(如果必须被杀死,则为 None),以及“actionTaken”。如果进程正常完成,这将是“SUBPROCESS2_PROCESS_COMPLETED”,或者是“SUBPROCESS2_PROCESS_TERMINATED”和 SUBPROCESS2_PROCESS_KILLED 的掩码,具体取决于所采取的操作(有关完整详细信息,请参阅文档)

        【讨论】:

          【解决方案20】:

          如果您使用的是 python 2,请尝试一下

          import subprocess32
          
          try:
              output = subprocess32.check_output(command, shell=True, timeout=3)
          except subprocess32.TimeoutExpired as e:
              print e
          

          【讨论】:

          • 可能无法在 Windows 上运行,正如最初的问题所问的那样
          【解决方案21】:

          此解决方案在 shell=True 的情况下杀死进程树,将参数传递给进程(或不传递),超时并获取回调的 stdout、stderr 和进程输出(它使用 psutil 作为 kill_proc_tree) .这是基于 SO 中发布的几个解决方案,包括 jcollado 的。在 jcollado 的回答中,Anson 和 jradice 对 cme​​ts 进行了回复。在 Windows Srvr 2012 和 Ubuntu 14.04 中测试。请注意,对于 Ubuntu,您需要将 parent.children(...) 调用更改为 parent.get_children(...)。

          def kill_proc_tree(pid, including_parent=True):
            parent = psutil.Process(pid)
            children = parent.children(recursive=True)
            for child in children:
              child.kill()
            psutil.wait_procs(children, timeout=5)
            if including_parent:
              parent.kill()
              parent.wait(5)
          
          def run_with_timeout(cmd, current_dir, cmd_parms, timeout):
            def target():
              process = subprocess.Popen(cmd, cwd=current_dir, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
          
              # wait for the process to terminate
              if (cmd_parms == ""):
                out, err = process.communicate()
              else:
                out, err = process.communicate(cmd_parms)
              errcode = process.returncode
          
            thread = Thread(target=target)
            thread.start()
          
            thread.join(timeout)
            if thread.is_alive():
              me = os.getpid()
              kill_proc_tree(me, including_parent=False)
              thread.join()
          

          【讨论】:

            【解决方案22】:

            有一个想法是继承 Popen 类并使用一些简单的方法装饰器对其进行扩展。我们称之为 ExpirablePopen。

            from logging import error
            from subprocess import Popen
            from threading import Event
            from threading import Thread
            
            
            class ExpirablePopen(Popen):
            
                def __init__(self, *args, **kwargs):
                    self.timeout = kwargs.pop('timeout', 0)
                    self.timer = None
                    self.done = Event()
            
                    Popen.__init__(self, *args, **kwargs)
            
                def __tkill(self):
                    timeout = self.timeout
                    if not self.done.wait(timeout):
                        error('Terminating process {} by timeout of {} secs.'.format(self.pid, timeout))
                        self.kill()
            
                def expirable(func):
                    def wrapper(self, *args, **kwargs):
                        # zero timeout means call of parent method
                        if self.timeout == 0:
                            return func(self, *args, **kwargs)
            
                        # if timer is None, need to start it
                        if self.timer is None:
                            self.timer = thr = Thread(target=self.__tkill)
                            thr.daemon = True
                            thr.start()
            
                        result = func(self, *args, **kwargs)
                        self.done.set()
            
                        return result
                    return wrapper
            
                wait = expirable(Popen.wait)
                communicate = expirable(Popen.communicate)
            
            
            if __name__ == '__main__':
                from subprocess import PIPE
            
                print ExpirablePopen('ssh -T git@bitbucket.org', stdout=PIPE, timeout=1).communicate()
            

            【讨论】:

              【解决方案23】:

              您可以使用select 来做到这一点

              import subprocess
              from datetime import datetime
              from select import select
              
              def call_with_timeout(cmd, timeout):
                  started = datetime.now()
                  sp = subprocess.Popen(cmd, stdout=subprocess.PIPE)
                  while True:
                      p = select([sp.stdout], [], [], timeout)
                      if p[0]:
                          p[0][0].read()
                      ret = sp.poll()
                      if ret is not None:
                          return ret
                      if (datetime.now()-started).total_seconds() > timeout:
                          sp.kill()
                          return None
              

              【讨论】:

                【解决方案24】:

                我遇到的问题是,如果多线程子进程花费的时间超过给定的超时长度,我想终止它。我想在Popen() 中设置超时,但它不起作用。然后,我意识到Popen().wait() 等于call(),所以我有了在.wait(timeout=xxx) 方法中设置超时的想法,终于奏效了。因此,我是这样解决的:

                import os
                import sys
                import signal
                import subprocess
                from multiprocessing import Pool
                
                cores_for_parallelization = 4
                timeout_time = 15  # seconds
                
                def main():
                    jobs = [...YOUR_JOB_LIST...]
                    with Pool(cores_for_parallelization) as p:
                        p.map(run_parallel_jobs, jobs)
                
                def run_parallel_jobs(args):
                    # Define the arguments including the paths
                    initial_terminal_command = 'C:\\Python34\\python.exe'  # Python executable
                    function_to_start = 'C:\\temp\\xyz.py'  # The multithreading script
                    final_list = [initial_terminal_command, function_to_start]
                    final_list.extend(args)
                
                    # Start the subprocess and determine the process PID
                    subp = subprocess.Popen(final_list)  # starts the process
                    pid = subp.pid
                
                    # Wait until the return code returns from the function by considering the timeout. 
                    # If not, terminate the process.
                    try:
                        returncode = subp.wait(timeout=timeout_time)  # should be zero if accomplished
                    except subprocess.TimeoutExpired:
                        # Distinguish between Linux and Windows and terminate the process if 
                        # the timeout has been expired
                        if sys.platform == 'linux2':
                            os.kill(pid, signal.SIGTERM)
                        elif sys.platform == 'win32':
                            subp.terminate()
                
                if __name__ == '__main__':
                    main()
                

                【讨论】:

                  【解决方案25】:

                  预先添加 Linux 命令 timeout 不是一个糟糕的解决方法,它对我有用。

                  cmd = "timeout 20 "+ cmd
                  subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                  (output, err) = p.communicate()
                  

                  【讨论】:

                  • 如何在子流程执行期间打印输出字符串? - 输出消息由子进程返回。
                  • timeout 在 mac 中默认不可用,因此不可移植
                  【解决方案26】:

                  Python 3.5 开始,有一个新的subprocess.run 通用命令(意在替换check_callcheck_output ...),它也有timeout= 参数。

                  subprocess.运行(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None强>,检查=假,编码=无,错误=无)

                  运行 args 描述的命令。等待命令完成,然后返回一个CompletedProcess 实例。

                  超时到期时会引发subprocess.TimeoutExpired 异常。

                  【讨论】:

                    【解决方案27】:

                    对于 python 2.6+,使用 gevent

                     from gevent.subprocess import Popen, PIPE, STDOUT
                    
                     def call_sys(cmd, timeout):
                          p= Popen(cmd, shell=True, stdout=PIPE)
                          output, _ = p.communicate(timeout=timeout)
                          assert p.returncode == 0, p. returncode
                          return output
                    
                     call_sys('./t.sh', 2)
                    
                     # t.sh example
                     sleep 5
                     echo done
                     exit 1
                    

                    【讨论】:

                      【解决方案28】:

                      python 2.7

                      import time
                      import subprocess
                      
                      def run_command(cmd, timeout=0):
                          start_time = time.time()
                          df = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                          while timeout and df.poll() == None:
                              if time.time()-start_time >= timeout:
                                  df.kill()
                                  return -1, ""
                          output = '\n'.join(df.communicate()).strip()
                          return df.returncode, output
                      

                      【讨论】:

                        【解决方案29】:

                        在 Python 3.7.8 中测试超时后捕获的输出示例:

                        try:
                            return subprocess.run(command, shell=True, capture_output=True, timeout=20, cwd=cwd, universal_newlines=True)
                        except subprocess.TimeoutExpired as e:
                            print(e.output.decode(encoding="utf-8", errors="ignore"))
                            assert False;
                        

                        异常 subprocess.TimeoutExpired 有输出和其他成员:

                        cmd - 用于生成子进程的命令。

                        timeout - 以秒为单位的超时。

                        output - 子进程的输出,如果它被 run() 或 检查输出()。否则,无。

                        stdout - 输出别名,与标准错误对称。

                        stderr - 子进程的 Stderr 输出(如果它被捕获) 跑步()。否则,无。

                        更多信息:https://docs.python.org/3/library/subprocess.html#subprocess.TimeoutExpired

                        【讨论】:

                          【解决方案30】:

                          仅限Linux 的延迟答案,但如果有人想使用subprocess.getstatusoutput(),而超时参数不可用,您可以在命令开头使用built-in Linux timeout,即:

                          import subprocess
                          
                          timeout = 25 # seconds
                          cmd = f"timeout --preserve-status --foreground {timeout} ping duckgo.com"
                          exit_c, out = subprocess.getstatusoutput(cmd)
                          
                          if (exit_c == 0):
                              print("success")
                          else:
                              print("Error: ", out)
                          

                          timeout 参数:

                          【讨论】:

                            猜你喜欢
                            • 2011-12-05
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2011-04-22
                            • 2016-03-16
                            相关资源
                            最近更新 更多