如何在Python中限制函数调用的执行时间

时间:2008-12-14 16:20:24

标签: python multithreading

我的代码中有一个与套接字相关的函数调用,该函数来自另一个模块,因此无法控制,问题是它偶尔会阻塞几个小时,这是完全不可接受的,我怎样才能限制函数的执行时间我的代码?我想解决方案必须使用另一个线程。

11 个答案:

答案 0 :(得分:74)

@ rik.the.vik答案的一个改进是使用with statement给超时函数一些语法糖:

import signal
from contextlib import contextmanager

class TimeoutException(Exception): pass

@contextmanager
def time_limit(seconds):
    def signal_handler(signum, frame):
        raise TimeoutException("Timed out!")
    signal.signal(signal.SIGALRM, signal_handler)
    signal.alarm(seconds)
    try:
        yield
    finally:
        signal.alarm(0)


try:
    with time_limit(10):
        long_function_call()
except TimeoutException as e:
    print("Timed out!")

答案 1 :(得分:36)

我不确定这可能是跨平台的,但使用信号和警报可能是一个很好的方式来看待这个。通过一些工作,您可以在任何情况下使其完全通用并且可用。

http://docs.python.org/library/signal.html

所以你的代码看起来像这样。

import signal

def signal_handler(signum, frame):
    raise Exception("Timed out!")

signal.signal(signal.SIGALRM, signal_handler)
signal.alarm(10)   # Ten seconds
try:
    long_function_call()
except Exception, msg:
    print "Timed out!"

答案 2 :(得分:17)

这是限制功能运行时间的Linux / OSX方式。这是因为您不想使用线程,并希望您的程序等到函数结束或时间限制到期。

from multiprocessing import Process
from time import sleep

def f(time):
    sleep(time)


def run_with_limited_time(func, args, kwargs, time):
    """Runs a function with time limit

    :param func: The function to run
    :param args: The functions args, given as tuple
    :param kwargs: The functions keywords, given as dict
    :param time: The time limit in seconds
    :return: True if the function ended successfully. False if it was terminated.
    """
    p = Process(target=func, args=args, kwargs=kwargs)
    p.start()
    p.join(time)
    if p.is_alive():
        p.terminate()
        return False

    return True


if __name__ == '__main__':
    print run_with_limited_time(f, (1.5, ), {}, 2.5) # True
    print run_with_limited_time(f, (3.5, ), {}, 2.5) # False

答案 3 :(得分:12)

我更喜欢上下文管理器方法,因为它允许在with time_limit语句中执行多个python语句。由于Windows系统没有SIGALARM,因此使用Timer

可以使用更便携且更直接的方法
from contextlib import contextmanager
import threading
import _thread

class TimeoutException(Exception):
    def __init__(self, msg=''):
        self.msg = msg

@contextmanager
def time_limit(seconds, msg=''):
    timer = threading.Timer(seconds, lambda: _thread.interrupt_main())
    timer.start()
    try:
        yield
    except KeyboardInterrupt:
        raise TimeoutException("Timed out for operation {}".format(msg))
    finally:
        # if the action ends in specified time, timer is canceled
        timer.cancel()

import time
# ends after 5 seconds
with time_limit(5, 'sleep'):
    for i in range(10):
        time.sleep(1)

# this will actually end after 10 seconds
with time_limit(5, 'sleep'):
    time.sleep(10)

这里的关键技术是使用_thread.interrupt_main来中断来自计时器线程的主线程。需要注意的是,主线程并不总是快速响应KeyboardInterrupt引发的Timer。例如,time.sleep()调用系统函数,以便在KeyboardInterrupt调用后处理sleep

答案 4 :(得分:7)

在信号处理程序中执行此操作很危险:在引发异常时您可能位于异常处理程序中,并使事物处于损坏状态。例如,

def function_with_enforced_timeout():
  f = open_temporary_file()
  try:
   ...
  finally:
   here()
   unlink(f.filename)

如果在此处引发异常(),则永远不会删除临时文件。

这里的解决方案是异步异常被推迟,直到代码不在异常处理代码(除外或最后一个块)内,但Python不这样做。

请注意,执行本机代码时不会中断任何操作;它只会在函数返回时中断它,所以这可能对这种特殊情况没有帮助。 (SIGALRM本身可能会中断阻塞的调用 - 但是套接字代码通常只是在EINTR之后重试。)

使用线程执行此操作是一个更好的主意,因为它比信号更便携。因为你正在启动一个工作线程并阻塞直到它完成,所以没有通常的并发担忧。不幸的是,没有办法异步地将异常传递给Python中的另一个线程(其他线程API可以做到这一点)。在异常处理程序中发送异常也会遇到同样的问题,并且需要相同的修复。

答案 5 :(得分:5)

您不必使用线程。您可以使用其他进程来执行阻止工作,例如,可能使用subprocess模块。如果你想在你的程序的不同部分之间共享数据结构,那么Twisted是一个很好的库,可以让你自己控制它,如果你关心阻塞并且期望遇到这个麻烦,我会推荐它。 Twisted的坏消息是你必须重写你的代码以避免任何阻塞,并且有一个公平的学习曲线。

可以使用线程来避免阻塞,但我认为这是最后的手段,因为它会让你暴露在一个痛苦的世界里。在考虑在生产中使用线程之前,请阅读一本关于并发性的好书,例如:让·培根的“并行系统”。我与一群用线程真正冷却高性能东西的人合作,除非我们确实需要,否则我们不会将线程引入项目。

答案 6 :(得分:4)

使用任何语言执行此操作的唯一“安全”方法是使用辅助进程执行超时操作,否则您需要以这样一种方式构建代码,使其自行安全地超时,例如,通过检查循环或类似过程中经过的时间。如果不能选择更改方法,则线程就不够了。

为什么呢?因为你冒着把事情弄得很糟糕的风险。如果线程在方法中被简单地杀死,那么持有的锁等等就会被保留,并且无法释放。

所以看看流程方式,看一下线程方式。

答案 7 :(得分:1)

这是我认为通过谷歌找到的超时功能,它适用于我。

自: http://code.activestate.com/recipes/473878/

def timeout(func, args=(), kwargs={}, timeout_duration=1, default=None):
    '''This function will spwan a thread and run the given function using the args, kwargs and 
    return the given default value if the timeout_duration is exceeded 
    ''' 
    import threading
    class InterruptableThread(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
            self.result = default
        def run(self):
            try:
                self.result = func(*args, **kwargs)
            except:
                self.result = default
    it = InterruptableThread()
    it.start()
    it.join(timeout_duration)
    if it.isAlive():
        return it.result
    else:
        return it.result   

答案 8 :(得分:1)

我通常更喜欢使用@ josh-lee

建议的上下文管理器

但是如果有人有兴趣将其作为装饰者实施,那么这里可以选择。

以下是它的样子:

import time
from timeout import timeout

class Test(object):
    @timeout(2)
    def test_a(self, foo, bar):
        print foo
        time.sleep(1)
        print bar
        return 'A Done'

    @timeout(2)
    def test_b(self, foo, bar):
        print foo
        time.sleep(3)
        print bar
        return 'B Done'

t = Test()
print t.test_a('python', 'rocks')
print t.test_b('timing', 'out')

这是timeout.py模块:

import threading

class TimeoutError(Exception):
    pass

class InterruptableThread(threading.Thread):
    def __init__(self, func, *args, **kwargs):
        threading.Thread.__init__(self)
        self._func = func
        self._args = args
        self._kwargs = kwargs
        self._result = None

    def run(self):
        self._result = self._func(*self._args, **self._kwargs)

    @property
    def result(self):
        return self._result


class timeout(object):
    def __init__(self, sec):
        self._sec = sec

    def __call__(self, f):
        def wrapped_f(*args, **kwargs):
            it = InterruptableThread(f, *args, **kwargs)
            it.start()
            it.join(self._sec)
            if not it.is_alive():
                return it.result
            raise TimeoutError('execution expired')
        return wrapped_f

输出:

python
rocks
A Done
timing
Traceback (most recent call last):
  ...
timeout.TimeoutError: execution expired
out

请注意,即使抛出TimeoutError,装饰方法也将继续在不同的线程中运行。如果您还希望此线程被停止"见:Is there any way to kill a Thread in Python?

答案 9 :(得分:1)

这里:获得所需效果的简单方法:

https://pypi.org/project/func-timeout

这救了我一命。

现在是一个关于它如何工作的例子:假设你有一个巨大的项目列表要处理,你正在对这些项目迭代你的函数。但是,由于某些奇怪的原因,您的函数卡在第 n 项上,而不会引发异常。您需要对其他项目进行处理,越多越好。在这种情况下,您可以设置处理每个项目的超时时间:

import time
import func_timeout


def my_function(n):
    """Sleep for n seconds and return n squared."""
    print(f'Processing {n}')
    time.sleep(n)
    return n**2


def main_controller(max_wait_time, all_data):
    """
    Feed my_function with a list of itens to process (all_data).

    However, if max_wait_time is exceeded, return the item and a fail info.
    """
    res = []
    for data in all_data:
        try:
            my_square = func_timeout.func_timeout(
                max_wait_time, my_function, args=[data]
                )
            res.append((my_square, 'processed'))
        except func_timeout.FunctionTimedOut:
            print('error')
            res.append((data, 'fail'))
            continue

    return res


timeout_time = 2.1  # my time limit
all_data = [i for i in range(1, 10)]  # the data to be processed

res = main_controller(timeout_time, all_data)
print(res)

答案 10 :(得分:0)

@ user2283347中的方法已经过测试,可以工作,但是我们希望摆脱追溯消息。使用Remove traceback in Python on Ctrl-C中的传递技巧,修改后的代码为:

from contextlib import contextmanager
import threading
import _thread

class TimeoutException(Exception): pass

@contextmanager
def time_limit(seconds):
    timer = threading.Timer(seconds, lambda: _thread.interrupt_main())
    timer.start()
    try:
        yield
    except KeyboardInterrupt:
        pass     
    finally:
        # if the action ends in specified time, timer is canceled
        timer.cancel()

def timeout_svm_score(i):
     #from sklearn import svm
     #import numpy as np
     #from IPython.core.display import display
     #%store -r names X Y
     clf = svm.SVC(kernel='linear', C=1).fit(np.nan_to_num(X[[names[i]]]), Y)
     score = clf.score(np.nan_to_num(X[[names[i]]]),Y)
     #scoressvm.append((score, names[i]))
     display((score, names[i])) 
     
%%time
with time_limit(5):
    i=0
    timeout_svm_score(i)
#Wall time: 14.2 s

%%time
with time_limit(20):
    i=0
    timeout_svm_score(i)
#(0.04541284403669725, '计划飞行时间')
#Wall time: 16.1 s

%%time
with time_limit(5):
    i=14
    timeout_svm_score(i)
#Wall time: 5h 43min 41s

我们可以看到这种方法可能需要很长的时间来中断计算,我们要求5秒钟,但要花5个小时才能解决。