Y a-t-il un moyen de tuer un fil en Python?
est-il possible de terminer un thread en cours d'exécution sans mettre/vérifier les drapeaux/sémaphores/etc.?
24 réponses
c'est généralement un mauvais modèle de tuer un fil brusquement, en Python et dans n'importe quelle langue. Pensez aux cas suivants:
- le fil conducteur est la tenue d'une ressource critique qui doit être fermé correctement
- le fil a créé plusieurs autres threads qui doivent être tués.
la bonne façon de gérer cela si vous pouvez vous le permettre (si vous gérez vos propres fils) est d'avoir un exit_request indique que chaque threads vérifie l'intervalle régulier pour voir s'il est temps pour lui de sortir.
par exemple:
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):
super(StoppableThread, self).__init__()
self._stop_event = threading.Event()
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
dans ce code, vous devez appeler stop() sur le thread quand vous voulez qu'il sorte, et attendre que le thread sorte correctement en utilisant join(). Le thread doit vérifier le drapeau d'arrêt à intervalles réguliers.
Il ya des cas cependant où vous avez vraiment besoin de tuer un fil. Un par exemple, lorsque vous enveloppez une bibliothèque externe qui est occupée pour de longs appels et que vous voulez l'interrompre.
le code suivant permet (avec quelques restrictions) de soulever une Exception dans un thread Python:
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 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 excpetion in the context of the
thread represented by this instance.
"""
_async_raise( self._get_my_tid(), exctype )
(basé sur Killable Threads de Tomer Filiba. La citation sur la valeur de retour de PyThreadState_SetAsyncExc
semble être d'une ancienne version de Python .)
As noté dans la documentation, Ce n'est pas une balle magique car si le thread est occupé en dehors de L'interpréteur Python, il n'attrapera pas l'interruption.
un bon usage de ce code est d'avoir le thread attraper une exception spécifique et effectuer le nettoyage. De cette façon, vous pouvez interrompre une tâche et avoir encore le nettoyage approprié.
il n'y a pas D'API officielle pour faire ça, non.
vous devez utiliser l'API plate-forme pour tuer le thread, par exemple Pthread_kill, ou TerminateThread. Vous pouvez accéder à cette API par exemple via pythonwin, ou via ctypes.
remarquez que c'est intrinsèquement dangereux. Il en résultera probablement des déchets non récupérables (à partir des variables locales des cadres de cheminée qui deviennent des déchets), et peut conduire à des blocages, si le fil tué a la GIL au moment où il est tué.
Un multiprocessing.Process
peut p.terminate()
dans les cas où je veux tuer un thread, mais ne veux pas utiliser les drapeaux/serrures/signaux/sémaphores/événements/quoi que ce soit, je fais la promotion des threads à des processus complets. Pour le code qui utilise juste quelques threads, le overhead n'est pas si mauvais.
E. G. cela est pratique pour terminer facilement les "threads" de helper qui exécutent le blocage I / O
la conversion est triviale: en code apparenté remplacer tous les threading.Thread
par multiprocessing.Process
et tous les queue.Queue
par multiprocessing.Queue
et ajouter les appels requis de p.terminate()
à votre processus parent qui veut tuer son enfant p
si vous essayez de terminer le programme entier, vous pouvez définir le thread comme un"démon". voir Thread.daemon
vous ne devez jamais tuer de force un fil sans coopérer avec elle.
tuer un thread supprime toutes les garanties que try/finally bloque mis en place afin que vous puissiez laisser les serrures verrouillées, les fichiers ouverts, etc.
la seule fois où vous pouvez argumenter que tuer des threads par la force est une bonne idée est de tuer un programme rapidement, mais jamais de threads simples.
Ceci est basé sur thread2 -- tuables threads (Python recette)
vous devez appeler PyThreadState_SetasyncExc(), qui n'est disponible que via les ctypes.
cela n'a été testé que sur Python 2.7.3, mais il est probable que cela fonctionne avec d'autres 2 récents.x libère.
import ctypes
def terminate_thread(thread):
"""Terminates a python thread from another thread.
:param thread: a threading.Thread instance
"""
if not thread.isAlive():
return
exc = ctypes.py_object(SystemExit)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
ctypes.c_long(thread.ident), exc)
if res == 0:
raise ValueError("nonexistent 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(thread.ident, None)
raise SystemError("PyThreadState_SetAsyncExc failed")
Comme d'autres l'ont mentionné, la norme est de fixer un stop drapeau. Pour quelque chose de léger (pas de sous-classement de Thread, pas de variable globale), un lambda callback est une option. (Noter les parenthèses dans 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()
le remplacement de print()
par une fonction pr()
qui tire toujours la chasse d'eau ( sys.stdout.flush()
) peut améliorer la précision de la sortie du réservoir.
(testé seulement sur Windows/Eclipse/Python3.3)
en Python, vous ne pouvez tout simplement pas tuer un fil directement.
si vous N'avez pas vraiment besoin d'avoir un Thread (!), ce que vous pouvez faire, au lieu d'utiliser le threading package , est d'utiliser la multiprocessing package . Ici, pour tuer un processus, vous pouvez simplement appeler la méthode:
yourProcess.terminate() # kill the process!
Python va tuer votre processus (sur Unix par le signal SIGTERM, alors que sur Windows par l'appel TerminateProcess()
). Faites attention de l'utiliser en utilisant une Queue ou un tuyau! (il peut corrompre les données dans la File d'attente/Pipe)
noter que le multiprocessing.Event
et le multiprocessing.Semaphore
fonctionnent exactement de la même manière que le threading.Event
et le threading.Semaphore
respectivement. En fait, les premiers sont des clones de ces derniers.
si vous avez vraiment besoin d'utiliser un Thread, il n'y a aucun moyen de le tuer directement. Ce que vous pouvez faire, cependant, est d'utiliser un "fil de démon" . En fait, en Python, un fil peut être marqué comme démon :
yourThread.daemon = True # set the Thread as a "daemon thread"
le programme principal se terminera lorsqu'il ne restera plus de threads vivants non-démon. En d'autres termes, quand votre thread principal (qui est, bien sûr, un thread non-daemon) terminera ses opérations, le programme se terminera même s'il y a encore quelques threads de daemon qui fonctionnent.
Note qu'il est nécessaire de définir un Thread comme daemon
avant que la méthode start()
s'appelle!
bien sûr, vous pouvez et devez utiliser daemon
même avec multiprocessing
. Ici, quand le processus principal sort, il tente de mettre fin à tous ses processus enfantins démoniaques.
enfin, veuillez noter que sys.exit()
et os.kill()
ne sont pas des choix.
vous pouvez tuer un thread en installant trace dans le thread qui va sortir le thread. Voir le lien ci-joint pour une mise en œuvre possible.
il vaut mieux ne pas tuer un fil. Une façon pourrait être d'introduire un bloc" essayer " dans le cycle du thread et de lancer une exception quand vous voulez arrêter le thread (par exemple un break/return/... qui arrête votre pendant / tandis/...). J'ai utilisé ça sur mon application et ça marche...
il est certainement possible de mettre en œuvre une méthode Thread.stop
comme le montre l'exemple de code suivant:
import sys
import threading
import time
class StopThread(StopIteration):
pass
threading.SystemExit = SystemExit, StopThread
class Thread2(threading.Thread):
def stop(self):
self.__stop = True
def _bootstrap(self):
if threading._trace_hook is not None:
raise ValueError('Cannot run thread with tracing!')
self.__stop = False
sys.settrace(self.__trace)
super()._bootstrap()
def __trace(self, frame, event, arg):
if self.__stop:
raise StopThread()
return self.__trace
class Thread3(threading.Thread):
def _bootstrap(self, stop_thread=False):
def stop():
nonlocal stop_thread
stop_thread = True
self.stop = stop
def tracer(*_):
if stop_thread:
raise StopThread()
return tracer
sys.settrace(tracer)
super()._bootstrap()
###############################################################################
def main():
test1 = Thread2(target=printer)
test1.start()
time.sleep(1)
test1.stop()
test1.join()
test2 = Thread2(target=speed_test)
test2.start()
time.sleep(1)
test2.stop()
test2.join()
test3 = Thread3(target=speed_test)
test3.start()
time.sleep(1)
test3.stop()
test3.join()
def printer():
while True:
print(time.time() % 1)
time.sleep(0.1)
def speed_test(count=0):
try:
while True:
count += 1
except StopThread:
print('Count =', count)
if __name__ == '__main__':
main()
la classe Thread3
semble exécuter le code environ 33% plus rapidement que la classe Thread2
.
from ctypes import *
pthread = cdll.LoadLibrary("libpthread-2.15.so")
pthread.pthread_cancel(c_ulong(t.ident))
t est votre Thread
objet.
Lire la source de python ( Modules/threadmodule.c
et Python/thread_pthread.h
) vous pouvez voir le Thread.ident
est un type pthread_t
, donc vous pouvez faire tout ce que pthread
peut faire en python utiliser libpthread
.
une chose que je veux ajouter est que si vous lisez la documentation officielle dans filetage lib Python , il est recommandé d'éviter l'utilisation de fils" démoniaques", quand vous ne voulez pas de fils se terminent abruptement, avec le drapeau que Paolo Rovelli mentionné .
de la documentation officielle:
Les fils de démonsont brusquement arrêtés à l'arrêt. Leurs ressources (telles que les fichiers ouverts, base de données transactions, etc.) peut ne pas être libérée correctement. Si vous voulez que vos threads s'arrêtent avec élégance, faites-les non-daemonic et utilisez un mécanisme de signalisation approprié tel qu'un événement.
je pense que la création de fils daemonic dépend de votre application, mais en général (et à mon avis) il est préférable d'éviter de les tuer ou de les rendre daemonic. Dans multiprocessing vous pouvez utiliser is_alive()
pour vérifier l'état du processus et "Terminer" pour les terminer (aussi vous éviter Gil problems). Mais vous pouvez trouver plus de problèmes, parfois, lorsque vous exécutez votre code dans Windows.
et rappelez-vous toujours que si vous avez" live threads", L'interpréteur Python Les exécutera. (À cause de ce démoniaque peut vous aider si ne pas la matière se termine abruptement).
je suis en retard à ce jeu, mais j'ai été aux prises avec une question similaire et ce qui suit semble à la fois résoudre le problème parfaitement pour moi et me permet de faire quelques vérification de l'état du fil de base et le nettoyage lorsque le sous-fil démonisé sort:
import threading
import time
import atexit
def do_work():
i = 0
@atexit.register
def goodbye():
print ("'CLEANLY' kill sub-thread with value: %s [THREAD: %s]" %
(i, threading.currentThread().ident))
while True:
print i
i += 1
time.sleep(1)
t = threading.Thread(target=do_work)
t.daemon = True
t.start()
def after_timeout():
print "KILL MAIN THREAD: %s" % threading.currentThread().ident
raise SystemExit
threading.Timer(2, after_timeout).start()
Donne:
0
1
KILL MAIN THREAD: 140013208254208
'CLEANLY' kill sub-thread with value: 2 [THREAD: 140013674317568]
C'est une mauvaise réponse, voir les commentaires
Voici comment le faire:
from threading import *
...
for thread in enumerate():
if thread.isAlive():
try:
thread._Thread__stop()
except:
print(str(thread.getName()) + ' could not be terminated'))
Donnez-lui quelques secondes, puis votre fil doit être arrêté. Vérifier également la méthode thread._Thread__delete()
.
je recommande une méthode thread.quit()
pour plus de commodité. Par exemple, si vous avez une socket dans votre thread, je vous recommande de créer une méthode quit()
dans votre socket-handle. classe, terminer la prise, puis exécuter un thread._Thread__stop()
à l'intérieur de votre quit()
.
démarrer le sous-thread avec setDaemon(True).
def bootstrap(_filename):
mb = ModelBootstrap(filename=_filename) # Has many Daemon threads. All get stopped automatically when main thread is stopped.
t = threading.Thread(target=bootstrap,args=('models.conf',))
t.setDaemon(False)
while True:
t.start()
time.sleep(10) # I am just allowing the sub-thread to run for 10 sec. You can listen on an event to stop execution.
print('Thread stopped')
break
alors qu'il est plutôt vieux, ce pourrait être une solution pratique pour certains:
un petit module qui étend la fonctionnalité du module de filetage -- permet à un thread de lever des exceptions dans le contexte d'un autre fil. En soulevant
SystemExit
, vous pouvez enfin tuer les fils python.
import threading
import ctypes
def _async_raise(tid, excobj):
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(excobj))
if res == 0:
raise ValueError("nonexistent 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(tid, 0)
raise SystemError("PyThreadState_SetAsyncExc failed")
class Thread(threading.Thread):
def raise_exc(self, excobj):
assert self.isAlive(), "thread must be started"
for tid, tobj in threading._active.items():
if tobj is self:
_async_raise(tid, excobj)
return
# the thread was alive when we entered the loop, but was not found
# in the dict, hence it must have been already terminated. should we raise
# an exception here? silently ignore?
def terminate(self):
# must raise the SystemExit type, instead of a SystemExit() instance
# due to a bug in PyThreadState_SetAsyncExc
self.raise_exc(SystemExit)
ainsi, il permet à un "thread de soulever des exceptions dans le contexte d'un autre thread" et de cette façon, le thread terminé peut gérer la terminaison sans vérifier régulièrement un indicateur d'abandon.
cependant, selon sa source originale , Il ya quelques problèmes avec ce code.
- l'exception ne sera soulevée que lors de l'exécution du bytecode python. Si votre thread appelle une fonction de blocage native/intégrée, la exception sera levée que lorsque l'exécution retourne à l'python code.
- il y a aussi un problème si la fonction intégrée appelle en interne PyErr_Clear(), ce qui annulerait effectivement votre exception en attente. Vous pouvez essayer de le soulever à nouveau.
- seuls les types d'exception peuvent être levés en toute sécurité. Les cas d'Exception sont susceptibles de causer un comportement inattendu, et sont donc limités.
- par exemple: t1.raise_exc (TypeError) et non t1.raise_exc(TypeError("bla")).
- IMHO c'est un bug, et je l'ai signalé comme un seul. Pour plus d'informations, http://mail.python.org/pipermail/python-dev/2006-August/068158.html
- j'ai demandé d'exposer cette fonction dans le module de thread intégré, mais puisque ctypes est devenu une bibliothèque standard (à partir de 2.5), et ce
la fonctionnalité n'est pas susceptible d'être mise en œuvre-agnostique, il peut être conservé
non exposés.
Pieter Hintjens -- l'un des fondateurs du projet ØMQ -- dit, en utilisant ØMQ et en évitant les primitives de synchronisation comme les serrures, les Mutex, les événements, etc., est la façon la plus saine et la plus sûre d'écrire des programmes multi-threadés:
http://zguide.zeromq.org/py:all#Multithreading-with-ZeroMQ
Cela inclut dire à un fil d'enfant, qu'il devrait annuler son travail. Cela se ferait en équipant le fil avec un ømq-socket et le sondage sur cette socket pour un message disant qu'il devrait annuler.
le lien fournit également un exemple de code Python multi-threadé avec ØMQ.
contournement suivant peut être utilisé pour tuer un thread:
kill_threads = False
def doSomething():
global kill_threads
while True:
if kill_threads:
thread.exit()
......
......
thread.start_new_thread(doSomething, ())
cela peut être utilisé même pour terminer les threads, dont le code est écrit dans un autre module, à partir du thread principal. Nous pouvons déclarer une variable globale dans ce module et l'utiliser pour terminer le/les thread (s) généré (s) dans ce module.
Je l'utilise habituellement pour terminer tous les threads à la sortie du programme. Ce n'est peut-être pas le moyen idéal pour terminer thread/s mais pourrait aider.
si vous appelez explicitement time.sleep()
comme partie de votre thread( par exemple, un sondage sur un service externe), une amélioration sur la méthode de Phillipe est d'utiliser le délai dans le event
's wait()
méthode partout où vous sleep()
par exemple:
import threading
class KillableThread(threading.Thread):
def __init__(self, sleep_interval=1):
super().__init__()
self._kill = threading.Event()
self._interval = sleep_interval
def run(self):
while True:
print("Do Something")
# If no kill signal is set, sleep for the interval,
# If kill signal comes in while sleeping, immediately
# wake up and handle
is_killed = self._kill.wait(self._interval)
if is_killed:
break
print("Killing Thread")
def kill(self):
self._kill.set()
puis l'exécuter
t = KillableThread(sleep_interval=5)
t.start()
# Every 5 seconds it prints:
#: Do Something
t.kill()
#: Killing Thread
l'avantage d'utiliser wait()
au lieu de sleep()
et de vérifier régulièrement l'événement est que vous pouvez programmer dans des intervalles plus longs de sommeil, le fil est arrêté presque immédiatement (alors que vous seriez autrement sleep()
ing) et à mon avis, le code pour la manipulation de sortie est beaucoup plus simple.
il y a une bibliothèque construite à cet effet, stopit . Bien que certaines des mises en garde énumérées ici s'appliquent toujours, au moins cette bibliothèque présente une technique régulière et reproductible pour atteindre l'objectif énoncé.
cela semble fonctionner avec pywin32 sur windows 7
my_thread = threading.Thread()
my_thread.start()
my_thread._Thread__stop()
vous pouvez exécuter votre commande dans un processus et ensuite la tuer en utilisant l'identifiant du processus. J'avais besoin de synchroniser entre deux thread dont l'un ne revient pas tout seul.
processIds = []
def executeRecord(command):
print(command)
process = subprocess.Popen(command, stdout=subprocess.PIPE)
processIds.append(process.pid)
print(processIds[0])
#Command that doesn't return by itself
process.stdout.read().decode("utf-8")
return;
def recordThread(command, timeOut):
thread = Thread(target=executeRecord, args=(command,))
thread.start()
thread.join(timeOut)
os.kill(processIds.pop(), signal.SIGINT)
return;
Si vous avez vraiment besoin de la capacité de tuer une sous-tâche, utilisez une autre application. multiprocessing
et gevent
soutiennent tous deux le meurtre sans distinction d'un "fil".
le threading de Python ne supporte pas l'annulation. N'essayez même pas. Votre code est très susceptible de bloquer, de corrompre ou de faire fuir la mémoire, ou d'avoir d'autres effets "intéressants" involontaires difficiles à déboguer qui se produisent rarement et de façon non décisive.