场景是需要函数或子进程在指定时间内完成,但不是最简单的time.sleep()的方式(这会造成每次都最大等待)
运行环境:
python 3.6+
linux
设置子进程运行超时时间
import subprocess
def test(_timeout=2):
# 这里ls -lrt可以换成自己的任意模块,这里stdout也可以是文件流
with subprocess.Popen("ls -lrt", shell=True, stdout=subprocess.PIPE) as p:
try:
bs=p.communicate(timeout=_timeout)[0]
if bs:
bs_split = eval(bs)
res = ''
for line in bs_split:
res += line.decode()
print(res)
except Exception as e:
# 未在指定时间内完成,此处会有异常。主进程在根据情况做处理
print(e)
p.terminate()
test(1)
这里是开启了pipe管道模式,让主进程与子进程进行交互。pipe管道有大小限制(默认64k),如果我们先使用Popen.poll()或者Popen.wait()后在readline或者readlines,在子进程输出数据较多的情况下很容易造成死锁,原因是子进程写满管道后需要等待管道中有空余空间才能再次写入。Popen.communicate 就能完成如此操作。这里推荐大家用subprocess ,因为类似os.popen等都是调用subprocess。子进程会有巨量输出的情况下,不能使用这种模式。
os.popen
def popen(cmd, mode="r", buffering=-1): if not isinstance(cmd, str): raise TypeError("invalid cmd type (%s, expected string)" % type(cmd)) if mode not in ("r", "w"): raise ValueError("invalid mode %r" % mode) if buffering == 0 or buffering is None: raise ValueError("popen() does not support unbuffered streams") import subprocess, io if mode == "r": proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, bufsize=buffering) return _wrap_close(io.TextIOWrapper(proc.stdout), proc) else: proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, bufsize=buffering) return _wrap_close(io.TextIOWrapper(proc.stdin), proc)
主线程中对函数设置超时时间
import signal
import time
from functools import wraps
import signal
class TimeoutException(Exception):
pass
def timeout_func(timeout=1, default_return=None):
def decorateFunction(function):
@wraps(function)
def wrapper(*args, **kv):
def handler(signum, frame):
raise TimeoutException()
# 设置回调handler
signal.signal(signal.SIGALRM, handler)
signal.alarm(timeout)
try:
result = function(*args, **kv)
except TimeoutException as e:
print(e)
result = default_return
finally:
signal.alarm(0)
signal.signal(signal.SIGALRM, signal.SIG_DFL)
return result
return wrapper
return decorateFunction
# 装饰器模式
@timeout_func(timeout=2, default_return=0)
def test_timeout(sec, a, b):
time.sleep(sec)
return a + b
print(test_timeout(1.99, 2, 3))
# 输出5
注意 : signal.alarm只有linux下才有,window下没有。signal.alarm设置了告警时间以及回调函数后,只会在主线程中进行中断回调。