python装饰器:三种函数超时机制
[问题]
在python中阻塞式地调用某个函数时,如果函数执行实行过长或者卡住是我们无法接受的,那么我们就需要一种机制:给函数设置一个“执行超时时间”,当函数执行时间超过设定的阈值时就自动退出。
[方案]
1.闹钟信号
[code lang=”python”]
#coding=utf-8
import functools
import signal
import threading
import time
class TimeOutException(Exception):
pass
def time_limit(timeout):
def wrapper(func):
def handle(signum, frame):
raise TimeOutException("running timeout!")
@functools.wraps(func)
def to_do(*args, **kwargs):
try:
signal.signal(signal.SIGALRM, handle)
signal.alarm(timeout) #开启闹钟信号
ret = func(*args, **kwargs)
signal.alarm(0) #关闭闹钟信号
return ret
except TimeOutException as e:
emsg="function(%s) execute timeout after %d second" % (func.__name__, timeout)
raise TimeOutException(emsg)
return to_do
return wrapper
def case_1():
print "—case_1—"
@time_limit(5)
def foo():
for sec in range(1, 10):
print "sec: %d" % sec
time.sleep(1)
try:
foo()
print ret
except Exception as e:
print e
def case_2():
print "—case_2—"
t =threading.Thread(target=case_1)
t.start()
t.join()
if __name__ == ‘__main__’:
case_1()
case_2()
[/code]
这种方案存在一个问题,信号机制只能在主线程中生效。
2.暴力kill线程
[code lang=”python”]
#coding=utf-8
import ctypes
import functools
import inspect
import signal
import threading
import time
class TimeOutException(Exception):
pass
def time_limit(timeout=0):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
def _async_raise(tid, exctype):
"""raises the exception, performs cleanup if needed"""
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
# """if it returns a number greater than one, you’re in trouble,
# and you should call it again with exc=NULL to revert the effect"""
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed")
class TimeLimit(threading.Thread):
def __init__(self):
super(TimeLimit, self).__init__()
self.error=None
self.result=None
def run(self):
try:
self.error=None
self.result=func(*args, **kwargs)
except Exception as e:
self.error = e
self.result = None
def stop(self):
try:
_async_raise(self.ident, SystemExit)
except Exception:
pass
t = TimeLimit()
t.setDaemon(True)
t.start()
if timeout > 0:
t.join(timeout)
else:
t.join()
if t.isAlive():
t.stop()
emsg="function(%s) execute timeout after %d second" % (func.__name__, timeout)
raise TimeOutException(emsg)
if t.error is not None:
raise t.error
return t.result
return wrapper
return decorator
def foo():
@time_limit(timeout=5)
def bar():
for sec in range(1, 10):
print "sec: %d" % sec
time.sleep(1)
try:
bar()
except Exception as e:
print e
if __name__ == "__main__":
foo()
[/code]
3.优雅地关闭线程
[code lang=”python”]
#coding=utf-8
import functools
import sys
import threading
import time
class KThread(threading.Thread):
"""A subclass of threading.Thread, with a kill()
method.
Come from:
Kill a thread in Python:
http://mail.python.org/pipermail/python-list/2004-May/260937.html
"""
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self, *args, **kwargs)
self.killed = False
def start(self):
"""Start the thread."""
self.__run_backup = self.run
self.run = self.__run # Force the Thread to install our trace.
threading.Thread.start(self)
def __run(self):
"""Hacked run function, which installs the
trace."""
sys.settrace(self.globaltrace)
self.__run_backup()
self.run = self.__run_backup
def globaltrace(self, frame, why, arg):
if why == ‘call’:
return self.localtrace
else:
return None
def localtrace(self, frame, why, arg):
if self.killed:
if why == ‘line’:
raise SystemExit()
return self.localtrace
def kill(self):
self.killed = True
class TimeOutException(Exception):
pass
def time_limit(timeout):
def decorator(func):
def to_do(func, args, kwargs, result):
result.append(func(*args, **kwargs))
@functools.wraps(func)
def wapper(*args, **kwargs):
result = []
_kwargs = {
‘func’: func,
‘args’: args,
‘kwargs’: kwargs,
‘result’: result
}
t = KThread(target=to_do, args=(), kwargs=_kwargs)
t.start()
t.join(timeout)
if t.isAlive():
t.kill()
emsg="function(%s) execute timeout after %d second" % (func.__name__, timeout)
raise TimeOutException(emsg)
else:
return result[0]
return wapper
return decorator
def foo():
@time_limit(5)
def bar():
for sec in range(1, 10):
print "sec: %d" % sec
time.sleep(1)
try:
bar()
except Exception as e:
print e
if __name__ == ‘__main__’:
foo()
[/code]
About 小卒子
做一个好人,但不傻
Required fields are marked *
Comment
姓名 *
电子邮件 *
站点