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
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 join
ing un autre thread - surtout pas totalement inutile blocage des appels, tels que les threads du démon join
ing . 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 ;-).
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.
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()
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
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.
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.
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)