Pregunta

he visto un montón de preguntas relacionadas con esto ... pero mi código funciona en Python 2.6.2 y no para trabajar en Python 2.6.5. ¿Me equivoco al pensar que todo el atexit "funciones registradas a través de este módulo no se llama cuando el programa es matado por una señal de" cosa no debe contar aquí porque me estoy poniendo la señal y se sale limpiamente? ¿Que está pasando aqui? ¿Cuál es la forma correcta de hacer esto?

import atexit, sys, signal, time, threading

terminate = False
threads = []

def test_loop():
    while True:
        if terminate:
            print('stopping thread')
            break
        else:
            print('looping')
            time.sleep(1)

@atexit.register
def shutdown():
    global terminate
    print('shutdown detected')
    terminate = True
    for thread in threads:
        thread.join()

def close_handler(signum, frame):
    print('caught signal')
    sys.exit(0)

def run():
    global threads
    thread = threading.Thread(target=test_loop)
    thread.start()
    threads.append(thread)

    while True:
        time.sleep(2)
        print('main')

signal.signal(signal.SIGINT, close_handler)

if __name__ == "__main__":
    run()

Python 2.6.2:

$ python halp.py 
looping
looping
looping
main
looping
main
looping
looping
looping
main
looping
^Ccaught signal
shutdown detected
stopping thread

Python 2.6.5:

$ python halp.py 
looping
looping
looping
main
looping
looping
main
looping
looping
main
^Ccaught signal
looping
looping
looping
looping
...
looping
looping
Killed <- kill -9 process at this point

El hilo principal en 2.6.5 parece no ejecutar las funciones atexit.

¿Fue útil?

Solución

La diferencia aquí es en realidad la raíz sin relación con ambas señales y atexit, sino más bien un cambio en el comportamiento de sys.exit.

Antes de alrededor de 2.6.5, sys.exit (más exactamente, SystemExit ser atrapado en el nivel superior) causaría que el intérprete de salida; Si las discusiones aún estaban corriendo, estarían terminados, al igual que con los hilos POSIX.

Alrededor 2.6.5, el comportamiento cambiado: el efecto de sys.exit ahora es esencialmente el mismo que volvía de la función principal del programa. Cuando lo hace que - en ambas versiones -. Espera el intérprete para todos los hilos a unir antes de salir

El cambio relevante es que ahora llama Py_Finalize wait_for_thread_shutdown() cerca de la parte superior, donde no lo hizo antes.

Este cambio de comportamiento no parece correcta, sobre todo porque ya no funciona como se documenta, que es simplemente: "Salir desde Python." El efecto práctico ya no está a la salida de Python, sino simplemente para salir del hilo. (Como nota al margen, sys.exit nunca ha salido de Python cuando se llama desde otro hilo, pero que divergance oscura del comportamiento documentado no justifica una mucho más grande.)

Me puede ver el atractivo de la nueva conducta: en lugar de dos formas de salir del hilo principal ( "salida y esperar a que las discusiones" y "salida inmediata"), sólo hay una, como sys.exit es esencialmente idéntica a simplemente regresar de la función de la parte superior. Sin embargo, es un cambio de última hora y diverge de comportamiento documentado, que es mucho mayor que eso.

Debido a este cambio, después de sys.exit desde el controlador de señal por encima de, el intérprete se sienta a esperar a que los hilos a la salida y a continuación, ejecuta los manipuladores atexit después de que hacen. Puesto que es el manejador sí que le dice a los hilos a la salida, el resultado es un callejón sin salida.

Otros consejos

Salir debido a una señal no es el mismo que sale de dentro de un manejador de señales. La captura de una señal y salir con sys.exit es una salida limpia, no una salida debido a un manejador de señales. Así que, sí, estoy de acuerdo que debería funcionar atexit manipuladores aquí -. Al menos en principio

Sin embargo, hay algo complicado de manejadores de señales: son completamente asíncrona. Ellos pueden interrumpir el flujo del programa en cualquier momento, entre cualquier código de operación VM. Tome este código, por ejemplo. (Esto se debe manejar como la misma forma que el código anterior;. He omitido por razones de brevedad código)

import threading
lock = threading.Lock()
def test_loop():
    while not terminate:
        print('looping')
        with lock:
             print "Executing synchronized operation"
        time.sleep(1)
    print('stopping thread')

def run():
    while True:
        time.sleep(2)
        with lock:
             print "Executing another synchronized operation"
        print('main')

Hay un problema serio aquí: una señal (por ejemplo, C ^.) Puede ser recibido mientras run () es la celebración de lock. Si eso sucede, su manejador de la señal se llevará a cabo con el bloqueo que aún permanecen. Va a continuación, esperar a que test_loop a la salida, y si ese hilo se espera para el bloqueo, se le punto muerto.

Esta es toda una categoría de problemas, y es por eso que un mucho de la API no dicen para llamar desde dentro manejadores de señales. En su lugar, se debe establecer un indicador para decirle al hilo principal para cerrar en un momento apropiado.

do_shutdown = False
def close_handler(signum, frame):
    global do_shutdown
    do_shutdown = True
    print('caught signal')

def run():
    while not do_shutdown:
        ...

Mi preferencia es evitar salir del programa con sys.exit por completo y de manera explícita a hacer limpieza en el punto de salida principal (por ejemplo. El final de carrera ()), pero se puede utilizar atexit aquí si quieres.

No estoy seguro si esto fue cambiado por completo, pero así es como he hecho mi atexit en 2.6.5


atexit.register(goodbye)

def goodbye():
    print "\nStopping..."
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top