1000 Stimmen

Gibt es eine Möglichkeit, einen Thread zu löschen?

Ist es möglich, einen laufenden Thread zu beenden, ohne irgendwelche Flags/Semaphoren/etc. zu setzen/zu überprüfen?

853voto

Philippe F Punkte 11065

Es ist generell ein schlechtes Muster, einen Thread abrupt zu beenden, in Python und in jeder Sprache. Denken Sie an die folgenden Fälle:

  • der Thread hält eine kritische Ressource, die ordnungsgemäß geschlossen werden muss
  • der Thread hat mehrere andere Threads erzeugt, die ebenfalls gelöscht werden müssen.

Wenn Sie es sich leisten können (wenn Sie Ihre eigenen Threads verwalten), ist es eine gute Möglichkeit, dies zu handhaben, indem Sie ein exit_request-Flag haben, das jeder Thread in regelmäßigen Abständen überprüft, um festzustellen, ob es Zeit ist, ihn zu beenden.

Zum Beispiel:

import threading

class StoppableThread(threading.Thread):
    """Thread class with a stop() method. The thread itself has to check
    regularly for the stopped() condition."""

    def __init__(self,  *args, **kwargs):
        super(StoppableThread, self).__init__(*args, **kwargs)
        self._stop_event = threading.Event()

    def stop(self):
        self._stop_event.set()

    def stopped(self):
        return self._stop_event.is_set()

In diesem Code sollten Sie Folgendes aufrufen stop() auf den Thread, wenn er beendet werden soll, und warten Sie, bis der Thread ordnungsgemäß beendet wird, indem Sie join() . Der Thread sollte in regelmäßigen Abständen das Stop-Flag überprüfen.

Es gibt jedoch Fälle, in denen Sie einen Thread wirklich beenden müssen. Ein Beispiel ist, wenn Sie eine externe Bibliothek wickeln, die mit langen Aufrufen beschäftigt ist, und Sie sie unterbrechen wollen.

Der folgende Code ermöglicht (mit einigen Einschränkungen) das Auslösen einer Exception in einem Python-Thread:

def _async_raise(tid, exctype):
    '''Raises an exception in the threads with id tid'''
    if not inspect.isclass(exctype):
        raise TypeError("Only types can be raised (not instances)")
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(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(ctypes.c_long(tid), None)
        raise SystemError("PyThreadState_SetAsyncExc failed")

class ThreadWithExc(threading.Thread):
    '''A thread class that supports raising an exception in the thread from
       another thread.
    '''
    def _get_my_tid(self):
        """determines this (self's) thread id

        CAREFUL: this function is executed in the context of the caller
        thread, to get the identity of the thread represented by this
        instance.
        """
        if not self.isAlive():
            raise threading.ThreadError("the thread is not active")

        # do we have it cached?
        if hasattr(self, "_thread_id"):
            return self._thread_id

        # no, look for it in the _active dict
        for tid, tobj in threading._active.items():
            if tobj is self:
                self._thread_id = tid
                return tid

        # TODO: in python 2.6, there's a simpler way to do: self.ident

        raise AssertionError("could not determine the thread's id")

    def raiseExc(self, exctype):
        """Raises the given exception type in the context of this thread.

        If the thread is busy in a system call (time.sleep(),
        socket.accept(), ...), the exception is simply ignored.

        If you are sure that your exception should terminate the thread,
        one way to ensure that it works is:

            t = ThreadWithExc( ... )
            ...
            t.raiseExc( SomeException )
            while t.isAlive():
                time.sleep( 0.1 )
                t.raiseExc( SomeException )

        If the exception is to be caught by the thread, you need a way to
        check that your thread has caught it.

        CAREFUL: this function is executed in the context of the
        caller thread, to raise an exception in the context of the
        thread represented by this instance.
        """
        _async_raise( self._get_my_tid(), exctype )

(Basierend auf Killable Threads von Tomer Filiba. Das Zitat über den Rückgabewert von PyThreadState_SetAsyncExc scheint von einer alte Version von Python .)

Wie in der Dokumentation erwähnt, ist dies kein Patentrezept, denn wenn der Thread außerhalb des Python-Interpreters beschäftigt ist, wird er die Unterbrechung nicht abfangen.

Ein gutes Verwendungsmuster für diesen Code ist es, den Thread eine bestimmte Ausnahme abfangen und die Bereinigung durchführen zu lassen. Auf diese Weise können Sie eine Aufgabe unterbrechen und trotzdem eine ordnungsgemäße Bereinigung durchführen.

184voto

cfi Punkte 10332

A multiprocessing.Process よろしい p.terminate()

In den Fällen, in denen ich einen Thread beenden, aber keine Flags/Locks/Signale/Semaphoren/Ereignisse/was auch immer verwenden möchte, mache ich die Threads zu vollwertigen Prozessen. Bei Code, der nur einige wenige Threads verwendet, ist der Overhead nicht so groß.

Dies ist z. B. praktisch, um Hilfs-Threads", die blockierende E/A ausführen, einfach zu beenden.

Die Umwandlung ist trivial: Ersetzen Sie im zugehörigen Code alle threading.Thread con multiprocessing.Process und alle queue.Queue con multiprocessing.Queue und fügen Sie die erforderlichen Aufrufe von p.terminate() an Ihren Elternprozess, der sein Kind töten will p

Siehe die Python-Dokumentation für multiprocessing .

Beispiel:

import multiprocessing
proc = multiprocessing.Process(target=your_proc_function, args=())
proc.start()
# Terminate the process
proc.terminate()  # sends a SIGTERM

141voto

Martin v. Löwis Punkte 120025

Es gibt keine offizielle API dafür, nein.

Sie müssen die Plattform-API verwenden, um den Thread zu beenden, z. B. pthread_kill oder TerminateThread. Sie können auf diese API z. B. über pythonwin oder über ctypes zugreifen.

Beachten Sie, dass dies von Natur aus unsicher ist. Es führt wahrscheinlich zu nicht sammelbarem Müll (aus lokalen Variablen der Stack-Frames, die zu Müll werden) und kann zu Deadlocks führen, wenn der Thread, der beendet wird, die GIL zum Zeitpunkt des Beendens hat.

107voto

schettino72 Punkte 2736

Wenn Sie versuchen, das gesamte Programm zu beenden, können Sie den Thread als "Daemon" einrichten, siehe Thread.daemon

91voto

Jon Coombs Punkte 1886

Wie bereits von anderen erwähnt, wird in der Regel ein Stop-Flag gesetzt. Für etwas Leichtgewichtiges (keine Unterklassifizierung von Thread, keine globale Variable), ist ein Lambda-Callback eine Option. (Beachten Sie die Klammern in if stop() .)

import threading
import time

def do_work(id, stop):
    print("I am thread", id)
    while True:
        print("I am thread {} doing something".format(id))
        if stop():
            print("  Exiting loop.")
            break
    print("Thread {}, signing off".format(id))

def main():
    stop_threads = False
    workers = []
    for id in range(0,3):
        tmp = threading.Thread(target=do_work, args=(id, lambda: stop_threads))
        workers.append(tmp)
        tmp.start()
    time.sleep(3)
    print('main: done sleeping; time to stop the threads.')
    stop_threads = True
    for worker in workers:
        worker.join()
    print('Finis.')

if __name__ == '__main__':
    main()

Ersetzen von print() mit einer pr() Funktion, die immer spült ( sys.stdout.flush() ) kann die Genauigkeit der Shell-Ausgabe verbessern.

(Nur unter Windows/Eclipse/Python3.3 getestet)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X