¿Por qué sys.exit () no sale cuando se llama dentro de un hilo en Python?

98

Esta podría ser una pregunta estúpida, pero estoy probando algunas de mis suposiciones sobre Python y estoy confundido sobre por qué el siguiente fragmento de código no se cerraría cuando se llamaba en el hilo, pero saldría cuando se llamaba en el hilo principal.

import sys, time
from threading import Thread

def testexit():
    time.sleep(5)
    sys.exit()
    print "post thread exit"

t = Thread(target = testexit)
t.start()
t.join()
print "pre main exit, post thread exit"
sys.exit()
print "post main exit"

Los documentos de sys.exit () establecen que la llamada debe salir de Python. Puedo ver en la salida de este programa que "post salida del hilo" nunca se imprime, pero el hilo principal sigue funcionando incluso después de que el hilo llama a salir.

¿Se está creando una instancia separada del intérprete para cada hilo y la llamada a exit () solo está saliendo de esa instancia separada? Si es así, ¿cómo gestiona la implementación de subprocesos el acceso a los recursos compartidos? ¿Qué pasa si quisiera salir del programa del hilo (no es que realmente quiera, pero para entenderlo)?

Shabbyrobe
fuente

Respuestas:

71

sys.exit()plantea la SystemExitexcepción, al igual que lo hace thread.exit(). Entonces, cuando sys.exit()genera esa excepción dentro de ese hilo, tiene el mismo efecto que llamar thread.exit(), por lo que solo sale el hilo.

rpkelly
fuente
23

¿Qué pasa si quiero salir del programa del hilo?

Además del método que Deestan describió, puede llamar os._exit(observe el guión bajo). Antes de usarlo, asegúrese de que usted entiende que lo hace no hay limpieza (como llamar __del__o similar).

Helmut Grohne
fuente
2
¿Vaciará las E / S?
Lorenzo Belli
1
os._exit (n): "Salir del proceso con estado n, sin llamar a los controladores de limpieza, vaciar los búferes stdio, etc."
Tim Richardson
Tenga en cuenta que cuando os._exitse usa dentro de las maldiciones, la consola no se restablece a un estado normal con esto. Tienes que ejecutar reseten el shell de Unix para solucionar este problema.
sjngm
22

¿Qué pasa si quisiera salir del programa del hilo (no es que realmente quiera, pero para entenderlo)?

Al menos en Linux puedes hacer:

os.kill(os.getpid(), signal.SIGINT)

Esto envía SIGINTa al hilo principal que genera un KeyboardInterrupt. Con eso tienes una limpieza adecuada. Incluso puedes manejar la señal si quieres.

Por cierto: en Windows solo puede enviar una SIGTERMseñal, que no se puede captar desde Python. En ese caso, simplemente puede usar os._exitcon el mismo efecto.

Chris
fuente
1
También funciona con maldiciones a diferencia de os._exit
sjngm
12

¿Es el hecho de que esté impreso "pre salida principal, post salida del hilo" lo que le molesta?

A diferencia de otros lenguajes (como Java) donde el análogo a sys.exit( System.exit, en el caso de Java) hace que la máquina virtual / proceso / intérprete se detenga de inmediato, Python sys.exitsolo lanza una excepción: una excepción de salida del sistema en particular.

Aquí están los documentos para sys.exit(solo print sys.exit.__doc__):

Salga del intérprete levantando SystemSalir (estado).
Si el estado se omite o Ninguno, el valor predeterminado es cero (es decir, éxito).
Si el estado es numérico, se utilizará como estado de salida del sistema.
Si es otro tipo de objeto, se imprimirá y el
estado de salida del sistema será uno (es decir, falla).

Esto tiene algunas consecuencias:

  • en un hilo, simplemente mata el hilo actual, no todo el proceso (asumiendo que llega hasta la parte superior de la pila ...)
  • los destructores de objetos ( __del__) se invocan potencialmente a medida que se desenrollan los marcos de pila que hacen referencia a esos objetos
  • finalmente los bloques se ejecutan a medida que la pila se desenrolla
  • puedes atrapar una SystemExitexcepción

La última es posiblemente la más sorprendente y es otra razón más por la que casi nunca debería tener una exceptdeclaración no calificada en su código Python.

Laurence Gonsalves
fuente
11

¿Qué pasa si quisiera salir del programa del hilo (no es que realmente quiera, pero para entenderlo)?

Mi método preferido es el paso de mensajes Erlang-ish. Ligeramente simplificado, lo hago así:

import sys, time
import threading
import Queue # thread-safe

class CleanExit:
  pass

ipq = Queue.Queue()

def testexit(ipq):
  time.sleep(5)
  ipq.put(CleanExit)
  return

threading.Thread(target=testexit, args=(ipq,)).start()
while True:
  print "Working..."
  time.sleep(1)
  try:
    if ipq.get_nowait() == CleanExit:
      sys.exit()
  except Queue.Empty:
    pass
Deestan
fuente
3
No necesitas un Queueaquí. Solo un simple boolservirá. El nombre clásico de esta variable es is_activey su valor predeterminado inicial es True.
Acumenus
3
Sí, estás en lo correcto. De acuerdo con effbot.org/zone/thread-synchronization.htm , modificar una bool(o cualquier otra operación atómica) funcionará perfectamente para este problema en particular. La razón por la que voy con Queues es que cuando se trabaja con agentes roscados tiendo a terminar necesitando varias señales diferentes ( flush, reconnect, exit, etc ...) casi inmediatamente.
Deestan