Question Y a-t-il un moyen de tuer un Thread en Python?


Est-il possible de terminer un thread en cours sans définir / vérifier les drapeaux / sémaphores / etc.?


585
2017-11-27 14:55


origine


Réponses:


C'est généralement un mauvais modèle de tuer un fil abruptement, en Python et dans n'importe quelle langue. Pensez aux cas suivants:

  • le thread détient une ressource critique qui doit être fermée correctement
  • le thread a créé plusieurs autres threads qui doivent également être détruits.

La bonne façon de gérer cela 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 sur un intervalle régulier pour voir s'il est temps qu'il sorte.

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 devriez appeler stop () sur le thread quand vous voulez le quitter, et attendre que le thread se termine correctement en utilisant join (). Le thread doit vérifier le drapeau d'arrêt à intervalles réguliers.

Il y a cependant des cas où vous devez vraiment tuer un thread. Un exemple est lorsque vous encapsulez une bibliothèque externe occupée pour des appels longs et que vous souhaitez l'interrompre.

Le code suivant permet (avec certaines restrictions) de déclencher 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(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 Threads meurtrables par Tomer Filiba. La citation sur la valeur de retour de PyThreadState_SetAsyncExc semble être d'un ancienne version de Python.)

Comme indiqué dans la documentation, ce n'est pas une solution miracle car si le thread est occupé en dehors de l'interpréteur Python, il n'attrape pas l'interruption.

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


535
2017-11-28 11:19



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

Vous devez utiliser l'API de plate-forme pour supprimer le thread, par ex. pthread_kill ou TerminateThread. Vous pouvez accéder à une API par exemple. via pythonwin, ou via des ctypes.

Notez que ceci est intrinsèquement dangereux. Cela conduira probablement à des ordures irrécupérables (à partir des variables locales des cadres de la pile qui deviennent des ordures), et peut conduire à des blocages, si le thread à tuer a le GIL au moment où il est tué.


98
2017-11-27 15:08



UNE multiprocessing.Process pouvez p.terminate()

Dans les cas où je veux tuer un thread, mais je ne veux pas utiliser flags / locks / signals / sémaphores / events / whatever, je promeus les threads à des processus complets. Pour le code qui n'utilise que quelques threads, le surcoût n'est pas si grave.

Par exemple. cela est pratique pour terminer facilement les "threads" d'assistance qui exécutent les E / S de blocage

La conversion est triviale: Dans le code correspondant remplacer tous threading.Thread avec multiprocessing.Process et tout queue.Queue avec multiprocessing.Queue et ajoutez les appels requis de p.terminate() à votre processus parent qui veut tuer son enfant p

Python doc


63
2017-10-13 09:38



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


56
2017-11-03 14:53



Vous ne devriez jamais tuer de force un fil sans coopérer avec lui.

Tuer un thread supprime toutes les garanties que les blocs try / finally ont mis en place afin de laisser les verrous verrouillés, les fichiers ouverts, etc.

La seule fois où vous pouvez argumenter que tuer des threads de force est une bonne idée est de tuer un programme rapidement, mais jamais des threads uniques.


29
2017-11-27 14:58



Ceci est basé sur thread2 - Threadables (recette Python)

Vous devez appeler PyThreadState_SetasyncExc (), qui n'est disponible que par l'intermédiaire de ctypes.

Cela a seulement été testé sur Python 2.7.3, mais il est susceptible de fonctionner avec d'autres versions 2.x récentes.

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

26
2018-03-07 15:23



Comme d'autres l'ont mentionné, la norme est de mettre un drapeau d'arrêt. Pour quelque chose de léger (pas de sous-classe de Thread, pas de variable globale), un callback lambda est une option. (Notez 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()

Remplacer print() avec un pr() fonction qui rince toujours (sys.stdout.flush()) peut améliorer la précision de la sortie de la coque.

(Seulement testé sur Windows / Eclipse / Python3.3)


25
2017-12-03 00:07



En Python, vous ne pouvez pas tuer un Thread directement.

Si vous n'avez pas vraiment besoin d'avoir un Thread (!), Ce que vous pouvez faire, au lieu d'utiliser le enfilage paquet , est d'utiliser le multitraitement paquet . Ici, pour tuer un processus, vous pouvez simplement appeler la méthode:

yourProcess.terminate()  # kill the process!

Python va tuer votre processus (sous Unix via le signal SIGTERM, sous Windows via TerminateProcess() appel). Faites attention à l'utiliser en utilisant une file d'attente ou un tuyau! (il peut corrompre les données dans la file d'attente / tuyau)

Notez que le multiprocessing.Event et le multiprocessing.Semaphore travailler exactement de la même manière de la 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 thread peut être marqué comme démon:

yourThread.daemon = True  # set the Thread as a "daemon thread"

Le programme principal quittera si aucun thread vivant non démon n'est laissé. En d'autres termes, lorsque votre thread principal (qui est, bien sûr, un thread non démon) terminera ses opérations, le programme quittera même s'il y a encore des threads de démon qui fonctionnent.

Notez qu'il est nécessaire de définir un thread comme daemon avant le start() méthode est appelée!

Bien sûr, vous pouvez, et devriez, utiliser daemon même avec multiprocessing. Ici, lorsque le processus principal se termine, il tente de terminer tous ses processus enfants démoniaques.

Enfin, s'il vous plaît, notez que sys.exit() et os.kill() ne sont pas des choix.


17
2018-03-03 12:42