872 votes

Existe-t-il un moyen de tuer un Thread en Python ?

Est-il possible de mettre fin à un thread en cours d'exécution sans activer/contrôler les drapeaux/semaphores/etc ?

760voto

Bluebird75 Points 4612

C'est généralement une mauvaise habitude de tuer un fil de discussion de manière abrupte, en Python et dans n'importe quel langage. Pensez aux cas suivants :

  • le fil retient une ressource critique qui doit être fermée correctement
  • ce fil a créé plusieurs autres fils qui doivent être tués aussi.

La meilleure façon de gérer ce problème si vous pouvez vous le permettre (si vous gérez vos propres threads) est d'avoir un drapeau exit_request que chaque thread vérifie à intervalles réguliers pour voir s'il est temps pour lui de se retirer.

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 = threading.Event()

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

    def stopped(self):
        return self._stop.isSet()

Dans ce code, vous devez appeler stop() sur le thread lorsque vous voulez qu'il se termine, et attendre que le thread se termine correctement en utilisant join(). Le thread doit vérifier le drapeau stop à intervalles réguliers.

Il y a cependant des cas où vous devez vraiment tuer un fil. Par exemple, lorsque vous enveloppez une bibliothèque externe qui est occupée par de longs appels et que vous voulez l'interrompre.

Le code suivant permet (avec quelques restrictions) de lever 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(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(tid, 0)
        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 )

Comme indiqué dans la documentation, il ne s'agit pas d'une solution miracle, car si le thread est occupé en dehors de l'interpréteur Python, il ne détectera pas l'interruption.

Un bon modèle d'utilisation de ce code est de faire en sorte que le thread attrape une exception spécifique et effectue le nettoyage. De cette façon, vous pouvez interrompre une tâche tout en assurant un nettoyage correct.

3 votes

Ce code ne fonctionne pas tout à fait. Vous devez convertir le tid en long comme : ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), None)

3 votes

@Bluebird75 : dans le premier exemple, pourquoi utiliser un threading.Event au lieu d'un simple attribut booléen stopped qui peut être vérifiée depuis l'intérieur du fil ?

105 votes

@Bluebird75 : En outre, je ne suis pas sûr de comprendre l'argument selon lequel les threads ne devraient pas être tués brusquement "parce que le thread pourrait détenir une ressource critique qui doit être fermée correctement" : cela est également vrai à partir d'un programme principal, et les programmes principaux peuvent être tués brusquement par l'utilisateur (Ctrl-C dans Unix, par exemple) - auquel cas ils essaient de gérer cette possibilité aussi bien que possible. Donc, je ne vois pas ce qu'il y a de spécial avec les threads, et pourquoi ils ne devraient pas recevoir le même traitement que les programmes principaux (à savoir qu'ils peuvent être tués brusquement). :) Pourriez-vous développer ce point ?

133voto

cfi Points 2775

A multiprocessing.Process peut p.terminate()

Dans les cas où je veux tuer un thread, mais que je ne veux pas utiliser les drapeaux/locks/signaux/semaphores/événements/autres, je promeus les threads en processus à part entière. Pour un code qui n'utilise que quelques threads, la surcharge n'est pas si importante.

Par exemple, cela permet de mettre fin facilement aux "threads" auxiliaires qui exécutent des E/S bloquantes.

La conversion est triviale : dans le code associé, remplacez tous les éléments threading.Thread avec multiprocessing.Process et tout queue.Queue avec multiprocessing.Queue et ajouter les appels nécessaires de p.terminate() à votre processus parent qui veut tuer son enfant p

Document Python

0 votes

Merci. J'ai remplacé queue.Queue par multiprocessing.JoinableQueue et suivi cette réponse : stackoverflow.com/a/11984760/911207

14 votes

multiprocessing est agréable, mais sachez que les arguments sont marinés selon le nouveau procédé. Donc, si l'un des arguments est quelque chose qui n'est pas récupérable (comme un fichier logging.log ), ce n'est peut-être pas une bonne idée d'utiliser la fonction multiprocessing .

2 votes

multiprocessing sont transférés au nouveau processus sous Windows, mais Linux utilise la bifurcation pour les copier (Python 3.7, je ne suis pas sûr des autres versions). Vous vous retrouverez donc avec un code qui fonctionne sous Linux mais qui soulève des erreurs de pickle sous Windows.

130voto

Martin v. Löwis Points 61768

Il n'y a pas d'API officielle pour faire cela, non.

Vous devez utiliser l'API de la 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 cela n'est pas sûr en soi. Elle conduira probablement à des déchets irrécupérables (à partir des variables locales des cadres de pile qui deviennent des déchets), et peut conduire à des blocages, si le thread qui est tué possède la GIL au moment où il est tué.

43 votes

Il se conduisent à des blocages si le thread en question détient le GIL.

93voto

schettino72 Points 793

Si vous essayez de mettre fin à l'ensemble du programme, vous pouvez définir le thread comme un "démon". voir Thread.daemon

0 votes

Cela n'a aucun sens. La documentation indique clairement que "ceci doit être défini avant que start() ne soit appelé, sinon RuntimeError est soulevé". Ainsi, si je veux tuer un thread qui n'était pas un démon à l'origine, comment puis-je utiliser ceci ?

40 votes

Raffi Je pense qu'il suggère que vous le définissiez à l'avance, sachant que lorsque votre thread principal se termine, vous voulez également que les threads du démon se terminent.

3 votes

La configuration d'un thread comme démon n'est-elle pas quelque chose que l'on fait dans le cas où l'on veut que le thread continue à fonctionner même si le programme principal s'arrête ?

33voto

Lasse V. Karlsen Points 148037

Il ne faut jamais tuer de force un fil de discussion sans y avoir coopéré.

Tuer un thread supprime toutes les garanties que les blocs try/finally mettent en place, de sorte que vous pouvez laisser des verrous verrouillés, des fichiers ouverts, etc.

La seule fois où l'on peut soutenir que tuer de force les threads est une bonne idée est pour tuer rapidement un programme, mais jamais un seul thread.

23 votes

Pourquoi est-ce si difficile de dire à un fil, s'il te plaît, tue-toi quand tu auras fini ta boucle actuelle... Je ne comprends pas.

5 votes

Il n'y a pas de mécanisme intégré au processeur pour identifier une "boucle" en tant que telle, le mieux que vous puissiez espérer est d'utiliser une sorte de signal que le code qui se trouve actuellement dans la boucle vérifiera une fois qu'il en sortira. La manière correcte de gérer la synchronisation des threads est par des moyens coopératifs, la suspension, la reprise et la mort des threads sont des fonctions qui sont destinées aux débogueurs et au système d'exploitation, pas au code d'application.

4 votes

@Mehdi : si j'écris (personnellement) le code dans le fil, oui, je suis d'accord avec vous. Mais il y a des cas où j'exécute des bibliothèques tierces, et je n'ai pas accès à la boucle d'exécution de ce code. C'est un cas d'utilisation de la fonctionnalité demandée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X