web-dev-qa-db-ja.com

Pythonで関数呼び出しの実行時間を制限する方法

私のコードにはソケット関連の関数呼び出しがあり、その関数は別のモジュールからのものであるため、制御不能です。問題は、時々数時間ブロックすることです。これは完全に受け入れられません。ソリューションは別のスレッドを利用する必要があると思います。

63
btw0

これがどのようにクロスプラットフォームであるかはわかりませんが、シグナルとアラームを使用することはこれを見る良い方法かもしれません。少しの作業で、これを完全に汎用的にすることができ、あらゆる状況で使用できます。

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!"
37
rik.the.vik

@ 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!")
80
Josh Lee

関数の実行時間を制限する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 __== '__main__':
    print run_with_limited_time(f, (1.5, ), {}, 2.5) # True
    print run_with_limited_time(f, (3.5, ), {}, 2.5) # False
17
Ariel Cabib

_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_の使用です。 1つの注意点は、メインスレッドがKeyboardInterruptによって発生したTimerに常に迅速に応答するとは限らないことです。たとえば、time.sleep()はシステム関数を呼び出すため、KeyboardInterruptsleep呼び出しの後に処理されます。

12
user2283347

シグナルハンドラー内からこれを行うことは危険です。例外が発生したときに例外ハンドラーの内部にいる可能性があり、破損状態のままになります。例えば、

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

Here()で例外が発生した場合、一時ファイルは削除されません。

ここでの解決策は、コードが例外処理コード(exceptまたはfinallyブロック)内になくなるまで非同期例外を延期することですが、Pythonはそうしません。

これにより、ネイティブコードの実行中に何も中断されないことに注意してください。関数が戻ったときにのみ中断するため、この特定のケースに役立つとは限りません。 (SIGALRM自体がブロックしている呼び出しを中断する可能性がありますが、通常、ソケットコードはEINTRの後に単純に再試行します。)

スレッドを使用してこれを行うことは、シグナルよりも移植性が高いため、より良いアイデアです。ワーカースレッドを開始し、終了するまでブロックするため、通常の同時実行性の心配はありません。残念ながら、Python(他のスレッドAPIがこれを行うことができます)で別のスレッドに非同期に例外を配信する方法はありません。同じ修正が必要です。

7
Glenn Maynard

スレッドを使用する必要はありません。別のプロセスを使用してブロッキング作業を行うことができます。たとえば、おそらく subprocess モジュールを使用します。プログラムの異なる部分間でデータ構造を共有したい場合、 Twisted はこれを自分で制御するための素晴らしいライブラリです。たくさん。 Twistedの悪いニュースは、ブロックを回避するためにコードを書き直さなければならないことです。また、学習曲線は公平です。

あなたはcanブロッキングを回避するためにスレッドを使用しますが、これは最後の手段と見なします。本番環境でのスレッドの使用について考える前に、並行性に関する優れた本を読んでください。ジャンベーコンの「コンカレントシステム」。私は、スレッドを使って本当に高性能なものをやっているたくさんの人々と仕事をしています。私たちは、本当に必要でない限り、プロジェクトにスレッドを導入しません。

5
Dickon Reed

任意の言語でこれを行う唯一の「安全な」方法は、セカンダリプロセスを使用してそのタイムアウト処理を行うことです。それ以外の場合は、たとえば、ループなどで経過した時間を確認します。メソッドの変更がオプションではない場合、スレッドでは不十分です。

どうして?物事を悪い状態のままにしておく危険があるからです。スレッドがメソッドの途中で単純に強制終了されると、保持されているロックなどが保持されるだけで、解放できません。

プロセスの方法を見て、notスレッドの方法を見てください。

ここに私がグーグル経由で見つけたと思うタイムアウト関数があり、それは私のために機能します。

From: 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   
1
monkut

私は通常、@ josh-leeが示唆するようにcontextmanagerの使用を好むでしょう

しかし、誰かがこれをデコレータとして実装することに興味がある場合は、代替手段があります。

これは次のようになります。

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がスローされても、装飾されたメソッドは別のスレッドで実行され続けることに注意してください。このスレッドを「停止」したい場合は、以下を参照してください。 Pythonでスレッドを強制終了する方法はありますか?

1
Seba