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

Python 如何限制函数调用的执行时间?,python,multithreading,Python,Multithreading,我的代码中有一个与套接字相关的函数调用,该函数来自另一个模块,因此我无法控制,问题是它偶尔会阻塞数小时,这是完全不可接受的,我如何限制代码中的函数执行时间?我猜解决方案必须使用另一个线程。您不必使用线程。您可以使用另一个进程来执行阻塞工作,例如,可能使用模块。如果您想在程序的不同部分之间共享数据结构,那么这是一个很好的库,可以让您自己控制它。如果您关心阻塞并且希望经常遇到这种问题,我建议您使用它。Twisted的坏消息是您必须重写代码以避免任何阻塞,并且有一个公平的学习曲线 您可以使用线程来避免

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

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

您可以使用线程来避免阻塞,但我认为这是最后的手段,因为它会让您承受整个世界的痛苦。在考虑在生产中使用线程之前,请阅读一本关于并发的好书,例如Jean Bacon的“并发系统”。我和一群人一起工作,他们用线程做非常酷的高性能的东西,除非我们真的需要线程,否则我们不会将线程引入到项目中

在任何语言中,做到这一点的唯一“安全”方法是使用辅助进程来完成超时操作,否则您需要以这样一种方式构建代码,使其自身安全超时,例如通过检查循环或类似过程中经过的时间。如果更改方法不是一个选项,那么线程就不够了

为什么??因为当你这样做的时候,你正冒着让事情处于糟糕状态的风险。如果线程只是在方法中间被终止,那么被保持的锁等将被保持,并且不能被释放


因此,请看流程方式,不要看线程方式。

我不确定这可能有多跨平台,但使用信号和警报可能是一种很好的方式。只要稍加努力,您就可以使其完全通用,并且在任何情况下都可用

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

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!"

这是一个超时函数,我想我是通过谷歌找到的,它对我很有用

发件人:


@rik.the.vik的答案的一个改进是使用为timeout函数提供一些语法糖:

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!")

从信号处理程序中执行此操作是危险的:在引发异常时,您可能在异常处理程序中,并使事物处于中断状态。比如说,

def function_with_enforced_timeout():
  f = open_temporary_file()
  try:
   ...
  finally:
   here()
   unlink(f.filename)
如果在此处引发异常(),则永远不会删除临时文件

这里的解决方案是将异步异常延迟到代码不在异常处理代码(except或finally块)内,但Python不会这样做

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


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

这里有一种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

我通常更喜欢使用@josh lee建议的contextmanager

但如果有人想把它作为一个装饰器来实现,这里有一个替代方案

下面是它的样子:

导入时间
从超时导入超时
类别测试(对象):
@超时(2)
def测试_a(自身、foo、bar):
印刷食品
时间。睡眠(1)
打印条
返回“完成”
@超时(2)
def测试_b(自身、foo、bar):
印刷食品
时间。睡眠(3)
打印条
返回“B完成”
t=测试()
打印t.test_a('python','rocks'))
打印t.test_b('计时','输出')
这是
timeout.py
模块:

导入线程
类时间错误(异常):
通过
类InterruptableThread(threading.Thread):
定义初始化(self,func,*args,**kwargs):
threading.Thread.\uuuuu init\uuuuuu(自)
self._func=func
self.\u args=args
自我。_kwargs=kwargs
self.\u结果=无
def运行(自):
self.\u result=self.\u func(*self.\u args,**self.\u kwargs)
@财产
def结果(自我):
返回自己的结果
类超时(对象):
定义初始(自我,秒):
自我。_秒=秒
定义调用(self,f):
def wrapped_f(*args,**kwargs):
it=可中断线程(f,*args,**kwargs)
it.start()
it.join(自我/秒)
如果没有,它是否还活着()
返回结果
raise TimeoutError('执行已过期')
返回
输出:

python
岩石
完成
时机
回溯(最近一次呼叫最后一次):
...
timeout.TimeoutError:执行已过期
出来

请注意,即使抛出了
TimeoutError
,修饰的方法也将继续在不同的线程中运行。如果您还希望“停止”此线程,请参阅:

我更喜欢上下文管理器方法,因为它允许在一个有时间限制的
语句中执行多个python语句。由于windows系统没有
SIGALARM
,因此可以使用
定时器

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)
这里的关键技术是使用
\u thread.interrupt\u main
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)
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
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)