web-dev-qa-db-fra.com

Mettre fin à un programme multi-thread python

Comment faire un programme multi-thread python réponse à l'événement clé Ctrl + C?

Edit: Le code est comme ceci:

import threading
current = 0

class MyThread(threading.Thread):
    def __init__(self, total):
        threading.Thread.__init__(self)
        self.total = total

    def stop(self):
        self._Thread__stop()

    def run(self):
        global current
        while current<self.total:
            lock = threading.Lock()
            lock.acquire()
            current+=1
            lock.release()
            print current

if __name__=='__main__':

    threads = []
    thread_count = 10
    total = 10000
    for i in range(0, thread_count):
        t = MyThread(total)
        t.setDaemon(True)
        threads.append(t)
    for i in range(0, thread_count):
        threads[i].start()

J'ai essayé de supprimer join () sur tous les threads mais cela ne fonctionne toujours pas. Est-ce parce que le segment de verrouillage à l'intérieur de la procédure run () de chaque thread?

Edit: Le code ci-dessus est censé fonctionner mais il s'est toujours interrompu lorsque la variable actuelle était dans la plage de 5000 à 6000 et à travers les erreurs comme ci-dessous

Exception in thread Thread-4 (most likely raised during interpreter shutdown):
Traceback (most recent call last):
  File "/usr/lib/python2.5/threading.py", line 486, in __bootstrap_inner
  File "test.py", line 20, in run
<type 'exceptions.TypeError'>: unsupported operand type(s) for +=: 'NoneType' and 'int'
Exception in thread Thread-2 (most likely raised during interpreter shutdown):
Traceback (most recent call last):
  File "/usr/lib/python2.5/threading.py", line 486, in __bootstrap_inner
  File "test.py", line 22, in run
71
jack

Faites de chaque thread sauf le principal un démon (t.daemon = True En 2.6 ou mieux, t.setDaemon(True) en 2.6 ou moins, pour chaque objet de thread t avant de le démarrer). De cette façon, lorsque le thread principal reçoit le KeyboardInterrupt, s'il ne l'attrape pas ou l'attrape mais décide de se terminer de toute façon, tout le processus se terminera. Voir la documentation .

edit : après avoir vu le code de l'OP (non publié à l'origine) et l'affirmation que "cela ne fonctionne pas", il semble que je doive ajouter. ..:

Bien sûr, si vous voulez que votre thread principal reste réactif (par exemple, pour control-C), ne l'encouragez pas à bloquer les appels, comme joining un autre thread - surtout pas totalement inutile blocage des appels, tels que les threads du démon joining . Par exemple, il suffit de changer la boucle finale dans le thread principal du courant (absolu et dommageable):

for i in range(0, thread_count):
    threads[i].join()

à quelque chose de plus sensé comme:

while threading.active_count() > 0:
    time.sleep(0.1)

si votre main n'a rien de mieux à faire que de terminer tous les threads par eux-mêmes ou de recevoir un contrôle-C (ou un autre signal).

Bien sûr, il existe de nombreux autres modèles utilisables si vous préférez que vos threads ne se terminent pas brusquement (comme le peuvent les threads démoniaques) - à moins que ils, aussi, soient embourbés pour toujours dans des appels de blocage inconditionnel , blocages et autres ;-).

90
Alex Martelli

Il y a deux façons principales, une propre et une simple.

La meilleure façon est d'attraper KeyboardInterrupt dans votre thread principal et de définir un indicateur que vos threads d'arrière-plan peuvent vérifier pour qu'ils sachent quitter; voici une version simple/légèrement désordonnée utilisant un global:

exitapp = False
if __== '__main__':
    try:
        main()
    except KeyboardInterrupt:
        exitapp = True
        raise

def threadCode(...):
    while not exitapp:
        # do work here, watch for exitapp to be True

Le moyen compliqué mais facile est d'attraper KeyboardInterrupt et d'appeler os._exit (), ce qui termine immédiatement tous les threads.

16
Walter Mundt

Un travailleur pourrait vous être utile:

#!/usr/bin/env python

import sys, time
from threading import *
from collections import deque

class Worker(object):
    def __init__(self, concurrent=1):
        self.concurrent = concurrent
        self.queue = deque([])
        self.threads = []
        self.keep_interrupt = False

    def _retain_threads(self):
        while len(self.threads) < self.concurrent:
            t = Thread(target=self._run, args=[self])
            t.setDaemon(True)
            t.start()
            self.threads.append(t)


    def _run(self, *args):
        while self.queue and not self.keep_interrupt:
            func, args, kargs = self.queue.popleft()
            func(*args, **kargs)

    def add_task(self, func, *args, **kargs):
        self.queue.append((func, args, kargs))

    def start(self, block=False):
        self._retain_threads()

        if block:
            try:
                while self.threads:
                    self.threads = [t.join(1) or t for t in self.threads if t.isAlive()]
                    if self.queue:
                        self._retain_threads()
            except KeyboardInterrupt:
                self.keep_interrupt = True
                print "alive threads: %d; outstanding tasks: %d" % (len(self.threads), len(self.queue))
                print "terminating..."


# example
print "starting..."
worker = Worker(concurrent=50)

def do_work():
    print "item %d done." % len(items)
    time.sleep(3)

def main():
    for i in xrange(1000):
        worker.add_task(do_work)
    worker.start(True)

main()
print "done."

# to keep Shell alive
sys.stdin.readlines()
5
Mark Ma

Vous pouvez toujours définir vos threads sur des threads "démon" comme:

t.daemon = True
t.start()

Et chaque fois que le fil principal meurt, tous les fils meurent avec lui.

http://www.regexprn.com/2010/05/killing-multithreaded-python-programs.html

4
Ehsanjs

Je préfère aller avec le code proposé dans ce billet de blog :

def main(args):

    threads = []
    for i in range(10):
        t = Worker()
        threads.append(t)
        t.start()

    while len(threads) > 0:
        try:
            # Join all threads using a timeout so it doesn't block
            # Filter out threads which have been joined or are None
            threads = [t.join(1000) for t in threads if t is not None and t.isAlive()]
        except KeyboardInterrupt:
            print "Ctrl-c received! Sending kill to threads..."
            for t in threads:
                t.kill_received = True

Ce que j'ai changé, c'est le t.join de t.join (1) à t.join (1000) . Le nombre réel de secondes n'a pas d'importance, sauf si vous spécifiez un numéro d'expiration, le thread principal restera sensible à Ctrl + C. Le except on KeyboardInterrupt rend le traitement du signal plus explicite.

4
Konrad Reiche

Si vous générez un Thread comme ceci - myThread = Thread(target = function) - puis faites myThread.start(); myThread.join(). Lorsque CTRL-C est lancé, le thread principal ne se ferme pas car il attend cet appel myThread.join() bloquant. Pour résoudre ce problème, mettez simplement un délai d'attente sur l'appel .join (). Le délai peut être aussi long que vous le souhaitez. Si vous voulez qu'il attende indéfiniment, mettez simplement un très long délai, comme 99999. C'est aussi une bonne pratique de faire myThread.daemon = True donc tous les threads se terminent lorsque le thread principal (non démon) se termine.

1
Milean
thread1 = threading.Thread(target=your_procedure, args = (arg_1, arg_2))    
try:
      thread1.setDaemon(True)  # very important
      thread1.start()
except (KeyboardInterrupt, SystemExit):
      cleanup_stop_thread();
      sys.exit()

Lorsque vous voulez tuer le fil, utilisez simplement:

thread1.join(0)
1
Charles P.