2009-10-28 23 views
67

¿Cómo hacer una respuesta de programa python multihilo al evento de tecla Ctrl + C?Terminar un programa python multihilo

Editar: El código es así:

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

traté de eliminar join() en todas las roscas pero todavía no funciona. ¿Es porque el segmento de bloqueo dentro del procedimiento run() de cada hilo?

Editar: El código anterior se supone que funciona, pero siempre interrumpe cuando la variable corriente estaba en 5.000-6.000 gama ya lo largo de los errores de la siguiente manera

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 
+0

Fwiw, estoy corriendo en [este mismo tema pero con el módulo 'concurrent.futures' nueva] (http://stackoverflow.com/q/29177490/877069). Todavía estoy tratando de averiguar si o cómo cualquiera de las soluciones aquí se traduce de 'threading' a' concurrent.futures'. –

Respuesta

85

hacer que cada hilo, excepto la principal una daemon (t.daemon = True en 2.6 o mejor, t.setDaemon(True) en 2.6 o menos, para cada objeto de hilo t antes de iniciarlo). De esta forma, cuando el hilo principal recibe KeyboardInterrupt, si no lo detecta o lo atrapa pero decide terminar de todos modos, todo el proceso terminará. Ver the docs.

edición: habiendo acaba de ver el código de la OP (no publicado originalmente) y la afirmación de que "no funciona", parece que tengo que añadir ...:

Por supuesto, si usted quiere que su hilo principal permanezca sensible (p. ej., control-C), no lo obstruya en llamadas de bloqueo, como join en otro hilo, especialmente no totalmente inutilizando bloqueando llamadas, como join ing daemon hilos. Por ejemplo, sólo cambia el último bucle en el hilo principal de la corriente (utterless y perjudicial):

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

a algo más sensato como:

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

si su principal no tiene nada mejor que hacer que bien para que todos los hilos terminen por sí mismos o para que se reciba un C de control (u otra señal).

Por supuesto, hay muchos otros patrones que pueden utilizarse si usted prefiere tener sus hilos no terminan bruscamente (como hilos pueden daemonic) - a menos que , también, están sumidos siempre en forma incondicional de bloqueo de llamadas, bloqueo de puertas, y similares;-).

+0

gracias por su respuesta, pero no funciona en mi código. – jack

+0

Ah, estás haciendo una llamada de bloqueo (inútil), así que por supuesto no hay respuesta al control-C. La solución es bastante simple: simplemente ** no ** haga inútiles llamadas de bloqueo si quiere mantenerse atento. Déjame editar mi respuesta para explicar. –

+0

completé más código en la publicación original. Probé tu método, puede detectar el evento KeyboardInterrupt pero el programa principal simplemente no se cierra. ¿Es causado por el segmento de bloqueo dentro del procedimiento run() de cada hilo? – jack

15

Existen dos formas principales, una limpia y otra sencilla.

La manera limpia es detectar KeyboardInterrupt en su hilo principal y establecer un indicador que los hilos de su fondo puedan verificar para que sepan que salen; aquí está una versión simple/ligeramente desordenado utilizando un mundial:

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

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

La forma desordenada, pero fácil es coger KeyboardInterrupt y llame os._exit(), que termina todas las discusiones inmediatamente.

+0

Muchas gracias, esto me estaba molestando intensamente :) –

+1

gracias, nota: es os._exit (0) en python3.x – Lazik

5

Un Trabajador podría ser útil para usted:

#!/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

preferiría ir con el código propuesto en this blog post:

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 

Lo que he cambiado es el t. unirse de t.join (1) a t.join (1000). El número real de segundos no importa, a menos que especifique un número de tiempo de espera, el hilo principal seguirá siendo sensible a Ctrl + C. El excepto en KeyboardInterrupt hace que el manejo de la señal sea más explícito.

+1

Este código se romperá después del primer ciclo ya que 't.join (1000)' no devolverá el hilo, sino 'None'. Por lo tanto, después del primer ciclo tendrá una lista de 'None''s en' threads'. – tmbo

+0

Esta es la mejor técnica para esperar que un hilo se una sin estar ocupado, esperando pero permitiendo un SIGINT – FujiApple

1

Si genera un hilo como tal - myThread = Thread(target = function) - y luego myThread.start(); myThread.join(). Cuando se inicia CTRL-C, el hilo principal no se cierra porque está esperando esa llamada de bloqueo myThread.join(). Para solucionar esto, simplemente ponga un tiempo de espera en la llamada .join(). El tiempo de espera puede ser el tiempo que desee. Si desea que espere indefinidamente, simplemente espere mucho tiempo, como 99999. También es una buena práctica hacer myThread.daemon = True para que todos los subprocesos salgan cuando el hilo principal (no demonio) finalice.

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

Cuando se quiere matar el hilo sólo tiene que utilizar:

thread1.join(0)