Python子进程超时?

19 投票
10 回答
41232 浏览
提问于 2025-04-16 04:16

有没有什么参数或者选项可以为Python的subprocess.Popen方法设置一个超时时间?

类似这样:

subprocess.Popen(['..'], ..., timeout=20) ?

10 个回答

5
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)

这个的输出应该是:

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不被支持。

7

subprocess.Popen这个东西不会让你的程序停下来,所以你可以像这样做:

import time

p = subprocess.Popen(['...'])
time.sleep(20)
if p.poll() is None:
  p.kill()
  print 'timed out'
else:
  print p.communicate()

不过它有个缺点,就是你必须至少等20秒才能让它完成。

16

我建议你看看Timer,它在threading模块里。我用它来给Popen设置一个超时。

首先,创建一个回调函数:

def timeout( p ):
    if p.poll() is None:
        print 'Error: process taking too long to complete--terminating'
        p.kill()

然后打开进程:

proc = Popen( ... )

接着创建一个定时器,这个定时器会调用回调函数,并把进程传给它。

t = threading.Timer( 10.0, timeout, [proc] )
t.start()
t.join()

在程序的某个地方,你可能想加上这一行:

t.cancel()

否则,Python程序会一直运行,直到定时器完成。

补充说明:有人提醒我,subprocessp可能在p.poll()p.kill()之间结束,这会造成竞争条件。我认为下面的代码可以解决这个问题:

import errno

def timeout( p ):
    if p.poll() is None:
        try:
            p.kill()
            print 'Error: process taking too long to complete--terminating'
        except OSError as e:
            if e.errno != errno.ESRCH:
                raise

不过,你可能想要整理一下异常处理,专门处理当子进程已经正常结束时出现的特定异常。

撰写回答