Question

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

Était-ce utile?

La solution

Il est généralement mauvais de tuer un fil de manière brutale, en Python et dans n’importe quel langage. Pensez aux cas suivants:

  • le thread contient une ressource critique qui doit être fermée correctement
  • le fil a créé plusieurs autres threads qui doivent également être tués.

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 indicateur exit_request que chaque thread vérifie à intervalles réguliers pour voir s’il est temps de quitter.

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 lorsque vous souhaitez le quitter et attendez que le thread se ferme correctement à l'aide de join () . Le fil doit vérifier le drapeau d’arrêt à intervalles réguliers.

Il existe cependant des cas où vous avez vraiment besoin de tuer un fil. Par exemple, vous encapsulez une bibliothèque externe occupée par de longs appels et souhaitez l'interrompre.

Le code suivant permet (avec certaines restrictions) de générer 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 )

(D'après Killable Threads de Tomer Filiba. Citation concernant la valeur de retour de PyThreadState_SetAsyncExc semble provenir d'un ancienne version de Python .)

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 n'interceptera pas l'interruption.

Un bon modèle d’utilisation de ce code consiste à demander au thread d’obtenir une exception spécifique et d’effectuer le nettoyage. De cette façon, vous pouvez interrompre une tâche tout en procédant au nettoyage approprié.

Autres conseils

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

Vous devez utiliser l'API de la plate-forme pour tuer le fil, par exemple. pthread_kill ou TerminateThread. Vous pouvez accéder à cette API, par exemple. via pythonwin ou via des ctypes.

Notez que cela est intrinsèquement dangereux. Cela entraînera probablement des déchets non récupérables (provenant de variables locales des cadres de la pile qui deviennent des déchets), et peut conduire à des blocages si le thread en cours de suppression a la GIL au point où il est tué.

Un processus multitraitement peut p.terminate ()

Dans les cas où je veux tuer un fil, mais que je ne veux pas utiliser de drapeaux / verrous / signaux / sémaphores / événements / peu importe, je promeune les threads en processus complets. Pour le code qui utilise seulement quelques threads, la surcharge n’est pas si grave.

E.g. cela est pratique pour terminer facilement les "threads" auxiliaires qui exécutent le blocage d'E / S

La conversion est simple: dans le code associé, remplacez tout threading.Thread par multiprocessing.Process et tout queue.Queue par multiprocessing.Queue et ajoutez les appels requis de p.terminate () à votre processus parent qui souhaite tuer son enfant p

doc Python

Si vous essayez de terminer l'ensemble du programme, vous pouvez définir le fil en tant que "démon". voir Thread.daemon

Ceci est basé sur les thread2 - threads coupables (recette Python)

Vous devez appeler PyThreadState_SetasyncExc (), qui n'est disponible que via les ctypes.

Ceci n'a été testé que 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")

Vous ne devez jamais tuer de force un thread sans coopérer avec celui-ci.

Tuer un thread supprime toute garantie que la configuration de bloque / enfin bloque, vous laissant ainsi les verrous verrouillés, les fichiers ouverts, etc.

La seule fois où vous pouvez affirmer qu'il est judicieux de supprimer de force les threads, c'est de tuer rapidement un programme, mais jamais de threads simples.

Comme d'autres l'ont déjà mentionné, la norme est de définir un drapeau d'arrêt. Pour quelque chose de léger (pas de sous-classe de Thread, pas de variable globale), un rappel lambda est une option. (Notez les parenthèses dans si 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 () par une fonction pr () toujours vidée ( sys.stdout.flush () ) peut améliorer la précision de la sortie du shell.

(testé uniquement sous Windows / Eclipse / Python3.3)

En Python, vous ne pouvez simplement pas tuer un thread directement.

Si vous n'avez PAS vraiment besoin d'un thread (!), ce que vous pouvez faire au lieu d'utiliser le Le package threading , consiste à utiliser le Package de multitraitement . 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 l'appel TerminateProcess () ). Faites attention à l'utiliser en utilisant une file d'attente ou un tuyau! (cela peut corrompre les données de la file d'attente / du tuyau)

Notez 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 devez VRAIMENT utiliser un fil, il n’ya aucun moyen de le tuer directement. Cependant, vous pouvez utiliser un "fil de démon" . En fait, en Python, un thread peut être marqué en tant que démon :

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

Le programme principal se fermera s'il ne reste plus de threads non-démon actifs. En d'autres termes, lorsque votre thread principal (qui est, bien sûr, un thread non démon) finira ses opérations, le programme se fermera même s'il reste encore des threads démon en marche.

Notez qu'il est nécessaire de définir un thread en tant que daemon avant que la méthode start () soit appelée!

Bien sûr, vous pouvez et devriez utiliser daemon même avec le multitraitement . Lorsque le processus principal se termine, il tente de mettre fin à tous ses processus enfants démoniaques.

Enfin, veuillez noter que sys.exit () et os.kill () ne sont pas des choix.

Vous pouvez tuer un thread en installant une trace dans le thread qui le quittera. Voir le lien ci-joint pour une implémentation possible.

Tuer un fil de discussion en Python

C’est mieux si vous ne tuez pas un fil. Une solution pourrait être d’introduire un " essayer " bloquer dans le cycle du thread et émettre une exception lorsque vous souhaitez arrêter le thread (par exemple, une pause / un retour / ... qui arrête votre pour / tout / ...). Je l'ai utilisé sur mon application et cela fonctionne ...

Il est certainement possible d'implémenter une méthode Thread.stop comme indiqué dans 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 objet Thread .

Lisez la source python ( Modules / threadmodule.c et Python / thread_pthread.h ), vous pouvez voir que le Thread.ident est un pthread_t , vous pouvez donc faire tout ce que pthread peut utiliser en python libpthread .

La solution de contournement suivante peut être utilisée pour tuer un thread:

kill_threads = False

def doSomething():
    global kill_threads
    while True:
        if kill_threads:
            thread.exit()
        ......
        ......

thread.start_new_thread(doSomething, ())

Ceci peut être utilisé même pour les threads de terminaison, 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 les threads générés dans ce module.

Je l'utilise généralement pour mettre fin à tous les threads à la sortie du programme. Ce n'est peut-être pas le moyen idéal pour terminer thread / s mais cela pourrait aider.

Je voudrais ajouter que si vous lisez la documentation officielle dans threading lib Python , il est recommandé d'éviter l'utilisation de " démoniaque " Lorsque vous ne voulez pas que les discussions se terminent abruptement, les drapeaux que Paolo Rovelli a mentionné .

D'après la documentation officielle:

  

Les threads de démon sont brusquement arrêtés à l’arrêt. Leurs ressources (telles que des fichiers ouverts, des transactions de base de données, etc.) peuvent ne pas être libérées correctement. Si vous souhaitez que vos threads s'arrêtent normalement, désactivez-les et utilisez un mécanisme de signalisation approprié, tel qu'un événement.

Je pense que la création de threads démoniaques 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 démoniaques. En multitraitement, vous pouvez utiliser is_alive () pour vérifier l’état du processus et "mettre fin à". pour les finir (vous évitez aussi les problèmes de GIL). 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 des "threads en direct", l'interpréteur Python sera exécuté pour les attendre. (En raison de ce démon, vous pouvez vous aider si cela ne compte pas brusquement, ça se termine).

Si vous appelez explicitement time.sleep () dans le cadre de votre thread (par exemple, interroger un service externe), une amélioration par rapport à la méthode de Phillipe consiste à utiliser le délai d'attente de l'événement < La méthode wait () de / code> où vous veillez ()

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, c'est que vous pouvez programmer dans des intervalles de sommeil plus longs, le thread est arrêté presque immédiatement (quand vous seriez autrement sleep () ing) et à mon avis, le code permettant de gérer les sorties est nettement plus simple.

Je suis bien en retard pour ce jeu, mais je me débattais avec une question similaire et le texte suivant apparaît. pour résoudre le problème parfaitement pour moi ET me permet de procéder à une vérification et à un nettoyage de base de l'état des threads lorsque le sous-thread démonisé se termine:

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

Rendements:

0
1
KILL MAIN THREAD: 140013208254208
'CLEANLY' kill sub-thread with value: 2 [THREAD: 140013674317568]

Il existe une bibliothèque construite à cet effet, stopit . Bien que certaines des précautions énumérées ci-après soient toujours d'application, cette bibliothèque présente au moins une technique répétable et régulière pour atteindre l'objectif indiqué.

Cela semble fonctionner avec pywin32 sous Windows 7

my_thread = threading.Thread()
my_thread.start()
my_thread._Thread__stop()
  

C'est une mauvaise réponse, voir les commentaires

Voici comment 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 devrait être arrêté. Vérifiez également la méthode thread._Thread__delete () .

Je recommanderais une méthode thread.quit () pour plus de commodité. Par exemple, si vous avez un socket dans votre thread, je vous conseillerais de créer une méthode quit () dans votre classe socket-handle, de terminer le socket, puis d'exécuter un thread ._Thread__stop () à l'intérieur de votre quit () .

Démarrez le sous-fil 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

Bien que plutôt ancien, cela pourrait être utile solution pour certains:

  

Un petit module qui étend les fonctionnalités du module de threading -   permet à un thread d'élever des exceptions dans le contexte d'un autre   fil. En élevant SystemExit , vous pouvez enfin tuer les threads 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 générer des exceptions dans le contexte d'un autre thread "" et de cette manière, le thread terminé peut gérer la terminaison sans vérifier régulièrement un indicateur d'abandon.

Toutefois, selon sa source d'origine , ce code présente quelques problèmes.

  
      
  • L'exception ne sera levée que lors de l'exécution du bytecode de python. Si votre thread appelle une fonction de blocage native / intégrée, le   exception ne sera levée que lorsque l'exécution retournera au python   code.      
        
    • Il existe également un problème si la fonction intégrée appelle en interne PyErr_Clear (), ce qui aurait pour effet d'annuler votre exception en attente.   Vous pouvez essayer de le relever à nouveau.
    •   
  •   
  • Seuls les types d'exception peuvent être levés en toute sécurité. Les instances d'exception sont susceptibles de provoquer un comportement inattendu et sont donc limitées.      
  •   
  • J'ai demandé à exposer cette fonction dans le module de thread intégré, mais depuis que ctypes est devenu une bibliothèque standard (à partir de la version 2.5), et ceci
      fonctionnalité n'est pas susceptible d'être agnostique à la mise en œuvre, elle peut être conservée
      non exposé.
  •   

Pieter Hintjens - l’un des fondateurs du ØMQ -project - dit: Utiliser ØMQ et éviter les primitives de synchronisation comme les verrous, les mutex, les événements, etc., est le moyen le plus sûr et le plus sûr d’écrire des programmes multithreads:

http://zguide.zeromq.org/py:all#Multithreading -with-ZeroMQ

Cela inclut de dire à un thread enfant qu'il doit annuler son travail. Cela se ferait en équipant le thread d’un ØMQ-socket et en interrogeant sur ce socket un message disant qu’il devrait s’annuler.

Le lien fournit également un exemple de code python multi-thread avec ØMQ.

Vous pouvez exécuter votre commande dans un processus, puis la tuer à l'aide de l'ID de processus. J'avais besoin de synchroniser entre deux threads 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 implémentation. multitraitement et gevent prennent en charge la suppression aveugle d'un "thread".

Les threads de Python ne supportent pas les annulations. N'essaye même pas. Votre code risque très probablement de se bloquer, de corrompre ou de fuir de la mémoire, ou d’avoir d’autres tentatives "intéressantes" inattendues. des effets difficiles à déboguer, qui se produisent rarement et de manière non déterministe.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top