Ist es möglich, einen laufenden Thread zu beenden, ohne irgendwelche Flags/Semaphoren/etc. zu setzen/zu überprüfen?
Antworten
Zu viele Anzeigen?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.
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
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.
Wenn Sie versuchen, das gesamte Programm zu beenden, können Sie den Thread als "Daemon" einrichten, siehe Thread.daemon
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)
- See previous answers
- Weitere Antworten anzeigen