No se puede matar el script de Python con Ctrl-C

117

Estoy probando el subproceso de Python con el siguiente script:

import threading

class FirstThread (threading.Thread):
    def run (self):
        while True:
            print 'first'

class SecondThread (threading.Thread):
    def run (self):
        while True:
            print 'second'

FirstThread().start()
SecondThread().start()

Esto se ejecuta en Python 2.7 en Kubuntu 11.10. Ctrl+ Cno lo matará. También intenté agregar un controlador para las señales del sistema, pero eso no ayudó:

import signal 
import sys
def signal_handler(signal, frame):
    sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)

Para matar el proceso, lo estoy matando por PID después de enviar el programa al fondo con Ctrl+ Z, que no se ignora. ¿Por qué Ctrl+ Cse ignora tan persistentemente? ¿Cómo puedo resolver esto?

dotancohen
fuente
@dotancohen, ¿está funcionando en Windows?
Kiriloff
@vitaibian: No he probado en Windows, pero parece que no es específico del sistema operativo.
dotancohen

Respuestas:

178

Ctrl+ Ctermina el hilo principal, pero debido a que sus hilos no están en modo demonio, continúan ejecutándose y eso mantiene vivo el proceso. Podemos convertirlos en demonios:

f = FirstThread()
f.daemon = True
f.start()
s = SecondThread()
s.daemon = True
s.start()

Pero luego hay otro problema: una vez que el hilo principal ha iniciado sus hilos, no hay nada más que pueda hacer. Entonces sale, y los hilos se destruyen instantáneamente. Así que mantengamos vivo el hilo principal:

import time
while True:
    time.sleep(1)

Ahora seguirá imprimiendo 'primero' y 'segundo' hasta que presione Ctrl+ C.

Editar: como han señalado los comentaristas, es posible que los subprocesos del demonio no tengan la oportunidad de limpiar cosas como archivos temporales. Si lo necesita, coja el KeyboardInterrupthilo principal y haga que coordine la limpieza y el apagado. Pero en muchos casos, dejar que los hilos del demonio mueran de repente es probablemente suficiente.

Thomas K
fuente
6
debe mencionar que al hacer esto, los hilos no se detienen correctamente y algunos recursos no se liberan.
Tommaso Barbugli
1
Bueno, Ctrl-C nunca es una forma elegante de detener nada. No estoy seguro de qué recursos quedarían, ¿no debería el sistema operativo reclamar nada cuando el proceso finaliza?
Thomas K
7
@ThomasK Los archivos temporales creados por tempfile.TemporaryFile()pueden dejarse en el disco, por ejemplo.
Feuermurmel
1
@ deed02392: No sé exactamente qué sucede con el hilo principal, pero hasta donde yo sé, no se puede hacer nada con él después de que sale. El proceso finalizará cuando hayan terminado todos los subprocesos que no son demonios; las relaciones entre padres e hijos no entran en eso.
Thomas K
4
Parece que en python3 puedes pasardaemon=True aThread.__init__
Ryan Haining
3

Creo que es mejor llamar a join () en tus hilos cuando esperas que mueran. Me he tomado un poco de libertad con su código para hacer que los bucles terminen (también puede agregar las necesidades de limpieza que se requieran allí). Se comprueba la verdad de la variable die en cada pasada y cuando es True, el programa se cierra.

import threading
import time

class MyThread (threading.Thread):
    die = False
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.name = name

    def run (self):
        while not self.die:
            time.sleep(1)
            print (self.name)

    def join(self):
        self.die = True
        super().join()

if __name__ == '__main__':
    f = MyThread('first')
    f.start()
    s = MyThread('second')
    s.start()
    try:
        while True:
            time.sleep(2)
    except KeyboardInterrupt:
        f.join()
        s.join()
Johan Snowgoose
fuente
while Truees una tontería, debería hacerlo joindirectamente, y esa función anulada es algo cuestionable. Tal vez def join(self, force=False): if force: self.die = Truepor eso join()no cambia por los join(force=True)mata. Pero incluso entonces, es mejor informar ambos hilos antes de unirse a cualquiera.
o11c
0

Una versión mejorada de la respuesta de @Thomas K:

  • Definir una función de asistente de is_any_thread_alive()acuerdo con esta esencia , que puede terminar main()automáticamente.

Códigos de ejemplo:

import threading

def job1():
    ...

def job2():
    ...

def is_any_thread_alive(threads):
    return True in [t.is_alive() for t in threads]

if __name__ == "__main__":
    ...
    t1 = threading.Thread(target=job1,daemon=True)
    t2 = threading.Thread(target=job2,daemon=True)
    t1.start()
    t2.start()

    while is_any_thread_alive([t1,t2]):
        time.sleep(0)
Hansimov
fuente