Question

J'ai vu beaucoup de questions à ce sujet ... mais mon code fonctionne sur python 2.6.2 et ne pour travailler sur python 2.6.5. Suis-je tort de penser que l'ensemble AtExit « fonctions enregistrées via ce module ne sont pas appelés lorsque le programme est tué par un signal » chose ne doit pas compter ici parce que je suis attraper le signal et la sortie proprement? Que se passe t-il ici? Quelle est la bonne façon de le faire?

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

Le fil principal 2.6.5 semble ne jamais exécuter les fonctions AtExit.

Était-ce utile?

La solution

La différence racine ici est en fait aucun lien avec les deux signaux et atexit, mais plutôt un changement dans le comportement des sys.exit.

Avant autour 2.6.5, sys.exit (avec plus de précision, SystemExit être pris au niveau supérieur) causerait l'interprète à la sortie; si les discussions étaient toujours en cours d'exécution, ils seraient terminés, tout comme avec threads POSIX.

Autour 2.6.5, le changement de comportement: l'effet de sys.exit est maintenant essentiellement le même que le retour de la fonction principale du programme. Lorsque vous faites que - dans les deux versions -. Les attentes d'un interprète pour tous les threads à assembler avant de quitter

Le changement important est que Py_Finalize appelle maintenant wait_for_thread_shutdown() près du sommet, où il n'a pas auparavant.

Ce changement de comportement semble incorrect, principalement parce qu'il ne fonctionne plus que documenté, ce qui est tout simplement: « Sortie de Python. » L'effet pratique est de ne plus sortir de Python, mais simplement pour quitter le fil. (Comme une note de côté, sys.exit n'a jamais quitté Python lorsqu'il est appelé d'un autre thread, mais que Divergence obscure de comportement documenté ne justifie pas un beaucoup plus grand.)

Je peux voir l'appel du nouveau comportement: plutôt que deux façons de quitter le thread principal ( « sortie et attente pour les fils » et « quitter immédiatement »), il n'y a qu'une seule, comme sys.exit est essentiellement identique à simplement retour de la fonction de haut. Cependant, il est un changement de rupture et diverge de comportement documenté, qui l'emporte bien que.

En raison de ce changement, après sys.exit du gestionnaire de signal ci-dessus, l'interprète est assis autour d'attente pour les fils de sortie, puis exécute les gestionnaires de atexit après qu'ils font. Comme il est le gestionnaire lui-même qui raconte les fils de sortie, le résultat est une impasse.

Autres conseils

Quitter due à un signal ne sont pas les mêmes que la sortie de à l'intérieur un gestionnaire de signaux. Attraper un signal et sortir avec sys.exit est une sortie propre, et non une sortie en raison d'un gestionnaire de signaux. Alors, oui, je suis d'accord qu'il devrait fonctionner AtExit gestionnaires ici -. Au moins en principe,

Cependant, il y a quelque chose délicate sur les gestionnaires de signal: ils sont complètement asynchrones. Ils peuvent interrompre le déroulement du programme à tout moment, entre un opcode VM. Prenez ce code, par exemple. (Traiter cela comme la même forme que votre code ci-dessus;. J'ai le code omis par souci de concision)

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

Il y a un sérieux problème: un signal (par exemple ^ C.) Peuvent être reçues pendant l'exécution () tient lock. Si cela se produit, votre gestionnaire de signal sera exécuté avec le verrou toujours détenu. Il va alors attendre test_loop pour sortir, et si ce thread attend le verrou, vous interblocage.

Ceci est une catégorie de problèmes ensemble, et c'est pourquoi beaucoup d'API disent de ne pas les appeler à l'intérieur des gestionnaires de signaux. , Vous devriez plutôt définir un indicateur pour dire le fil conducteur de fermer à un moment approprié.

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

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

Ma préférence est d'éviter de quitter le programme avec sys.exit entièrement et à faire explicitement le nettoyage au point de sortie principale (par exemple. La fin de course ()), mais vous pouvez utiliser atexit ici si vous voulez.

Je ne sais pas si cela a été entièrement changé, mais voilà comment je l'ai fait dans mon atexit 2.6.5


atexit.register(goodbye)

def goodbye():
    print "\nStopping..."
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top