it-swarm.com.de

So begrenzen Sie die Ausführungszeit eines Funktionsaufrufs in Python

In meinem Code gibt es einen Funktionsaufruf im Zusammenhang mit Sockets. Diese Funktion stammt von einem anderen Modul und ist daher nicht in meiner Hand. Das Problem besteht darin, dass sie gelegentlich stundenlang blockiert. Dies ist völlig inakzeptabel. Wie kann ich die Ausführungszeit für Funktionen in meinem Code begrenzen? Ich denke, die Lösung muss einen anderen Thread verwenden.

63
btw0

Ich bin mir nicht sicher, wie plattformübergreifend dies sein könnte, aber die Verwendung von Signalen und Alarmen könnte eine gute Sichtweise darauf sein. Mit ein wenig Arbeit können Sie dies auch komplett generisch und in jeder Situation nutzbar machen.

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

Ihr Code wird also ungefähr so ​​aussehen.

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

Eine Verbesserung der Antwort von @ rik.the.vik wäre, die Anweisung with zu verwenden, um der Timeout-Funktion einen syntaktischen Zucker zu geben:

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

Hier ist ein Linux/OSX-Weg, um die Laufzeit einer Funktion zu begrenzen. Dies ist der Fall, wenn Sie keine Threads verwenden möchten und Ihr Programm warten soll, bis die Funktion beendet ist oder das Zeitlimit abläuft.

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

Ich bevorzuge einen Kontextmanager-Ansatz, weil er die Ausführung mehrerer python Anweisungen innerhalb einer with time_limit - Anweisung ermöglicht. Weil das Windows-System nicht SIGALARM hat, ein portableres und Möglicherweise ist die Verwendung eines Timer eine einfachere Methode.

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)

Die Schlüsseltechnik ist hier die Verwendung von _thread.interrupt_main, Um den Hauptthread vom Timer-Thread zu unterbrechen. Eine Einschränkung besteht darin, dass der Haupt-Thread nicht immer schnell auf das KeyboardInterrupt reagiert, das vom Timer ausgelöst wurde. Zum Beispiel ruft time.sleep() eine Systemfunktion auf, sodass KeyboardInterrupt nach dem sleep -Aufruf behandelt wird.

12
user2283347

Dies von einem Signal-Handler aus zu tun, ist gefährlich: Möglicherweise befinden Sie sich zu dem Zeitpunkt, an dem die Ausnahme ausgelöst wird, in einem Ausnahmehandler und lassen die Dinge in einem fehlerhaften Zustand. Beispielsweise,

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

Wenn Ihre Ausnahme hier () ausgelöst wird, wird die temporäre Datei niemals gelöscht.

Die Lösung besteht darin, asynchrone Ausnahmen so lange zu verschieben, bis sich der Code nicht mehr im Code für die Ausnahmebehandlung befindet (Ausnahme- oder Endblock), aber Python tut dies nicht.

Beachten Sie, dass dies beim Ausführen von nativem Code keine Unterbrechungen verursacht. es wird nur unterbrochen, wenn die Funktion zurückkehrt, daher kann dies in diesem speziellen Fall nicht helfen. (SIGALRM selbst unterbricht möglicherweise den blockierenden Anruf, aber der Socket-Code versucht es in der Regel nach einem EINTR erneut.)

Dies mit Threads zu tun, ist eine bessere Idee, da es portabler ist als Signale. Da Sie einen Arbeitsthread starten und blockieren, bis er beendet ist, gibt es keine der üblichen Sorgen um die Parallelität. Leider gibt es keine Möglichkeit, eine Ausnahme asynchron an einen anderen Thread in Python zu senden (andere Thread-APIs können dies tun). Es gibt auch das gleiche Problem beim Senden einer Ausnahme während einer Ausnahmebehandlung und es ist das gleiche Update erforderlich.

7
Glenn Maynard

Sie müssen keine Threads verwenden. Sie können einen anderen Prozess verwenden, um die Blockierungsarbeit auszuführen, z. B. das Modul nterprozess . Wenn Sie Datenstrukturen zwischen verschiedenen Teilen Ihres Programms austauschen möchten, dann ist Twisted eine großartige Bibliothek, um sich die Kontrolle darüber zu verschaffen, und ich würde es empfehlen, wenn Sie sich für das Blockieren interessieren und dieses Problem erwarten viel. Die schlechte Nachricht bei Twisted ist, dass Sie Ihren Code neu schreiben müssen, um Blockierungen zu vermeiden, und es gibt eine faire Lernkurve.

Sie können Threads verwenden , um ein Blockieren zu vermeiden, aber ich würde dies als letzten Ausweg betrachten, da Sie dadurch einer ganzen Welt des Schmerzes ausgesetzt werden. Lesen Sie ein gutes Buch über Parallelität, bevor Sie überhaupt über die Verwendung von Threads in der Produktion nachdenken, z. Jean Bacons "Concurrent Systems". Ich arbeite mit ein paar Leuten zusammen, die wirklich coole High-Performance-Sachen mit Threads machen, und wir bringen keine Threads in Projekte ein, es sei denn, wir brauchen sie wirklich.

5
Dickon Reed

Der einzige "sichere" Weg, dies in jeder Sprache zu tun, ist die Verwendung eines sekundären Prozesses, um dieses Timeout-Ding auszuführen. Andernfalls müssen Sie Ihren Code so erstellen, dass er von sich aus sicher abläuft, zum Beispiel durch Überprüfen der verstrichenen Zeit in einer Schleife oder ähnlichem. Wenn das Ändern der Methode keine Option ist, reicht ein Thread nicht aus.

Warum? Weil du riskierst, Dinge in einem schlechten Zustand zu lassen, wenn du das tust. Wenn der Thread einfach mitten in der Methode beendet wird, werden Sperren, die gehalten werden, usw. nur gehalten und können nicht freigegeben werden.

Also schau dir den Prozess an, mach nicht schau dir den Thread an.

Hier ist eine Timeout-Funktion, die ich über Google gefunden habe und die bei mir funktioniert.

Von: 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

Normalerweise bevorzuge ich die Verwendung eines Kontextmanagers, wie von @ josh-lee vorgeschlagen

Für den Fall, dass jemand Interesse daran hat, dies als Dekorateur zu implementieren, ist hier eine Alternative.

So würde es aussehen:

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')

Und das ist der timeout.py Modul:

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

Die Ausgabe:

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

Beachten Sie, dass die dekorierte Methode auch dann in einem anderen Thread ausgeführt wird, wenn TimeoutError ausgelöst wird. Wenn Sie auch möchten, dass dieser Thread "gestoppt" wird, lesen Sie: Gibt es eine Möglichkeit, einen Thread in Python zu beenden?

1
Seba