Domanda

Ho visto un sacco di domande relative a questo ... ma il mio codice lavori su Python 2.6.2 e non al lavoro su Python 2.6.5. Mi sbaglio nel pensare che l'intero atexit "funzioni registrate tramite questo modulo non sono chiamati quando il programma viene ucciso da un segnale di" cosa non dovrebbe contare qui perché sto cattura il segnale e poi uscire in modo pulito? Cosa sta succedendo qui? Che cosa è il modo corretto di fare questo?

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

Il filo principale 2.6.5 sembra mai eseguire le funzioni atexit.

È stato utile?

Soluzione

La differenza principale qui è realmente estraneo a entrambi i segnali e atexit, ma piuttosto un cambiamento nel comportamento di sys.exit.

Prima attorno 2.6.5, sys.exit (più precisamente, SystemExit essere stato catturato al livello alto) causerebbe l'interprete di uscita; se le discussioni erano ancora in funzione, sarebbero terminati, proprio come con i thread POSIX.

Intorno 2.6.5, il comportamento cambiato: l'effetto di sys.exit ora è essenzialmente la stessa di ritorno dalla funzione principale del programma. Quando si esegue che - in entrambe le versioni -. Attende interprete per tutte le discussioni di essere raggiunto prima di uscire

Il cambiamento rilevante è che Py_Finalize ora chiama wait_for_thread_shutdown() vicino alla parte superiore, dove non ha fatto prima.

Questo cambiamento comportamentale sembra errato, in primo luogo perché non funziona più come documentato, che è semplicemente: "Uscire da Python" L'effetto pratico non è per uscire da Python, ma semplicemente per uscire dal filo. (Come nota a margine, sys.exit non è mai uscito Python quando viene chiamato da un altro thread, ma che oscura Divergenza dal comportamento documentato non giustifica uno molto più grande.)

I può vedere il richiamo del nuovo comportamento: anziché due vie d'uscita del filo principale ( "uscita e attendere fili" e "uscita immediatamente"), c'è solo uno, come sys.exit è essenzialmente identico al semplicemente ritorno dalla funzione superiore. Tuttavia, si tratta di una modifica sostanziale e diverge dal comportamento documentato, che supera di gran lunga quello.

A causa di questo cambiamento, dopo sys.exit dal gestore di segnale sopra, l'interprete si siede in attesa che le discussioni per uscire e quindi esegue i gestori atexit dopo che fanno. Dal momento che è il gestore stesso che racconta i fili per uscire, il risultato è una situazione di stallo.

Altri suggerimenti

Uscita grazie ad un segnale non è la stessa uscita dalla all'interno un gestore di segnale. Facendo un segnale e in uscita con sys.exit è un'uscita pulita, non un uscita a causa di un gestore di segnale. Quindi, sì, sono d'accordo che dovrebbe funzionare atexit gestori di qui -., Almeno in linea di principio

Tuttavia, c'è qualcosa di difficile di gestori di segnali: sono completamente asincrono. Essi possono interrompere il flusso del programma in qualsiasi momento, tra una qualsiasi codice operativo VM. Prendete questo codice, ad esempio. (Trattare questo come la stessa forma come il vostro codice di cui sopra;. Ho omesso il codice per brevità)

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

C'è un grave problema qui: un segnale (ad es ^ C.) Possono essere ricevuti durante la run () è in possesso di lock. Se ciò accade, il vostro gestore di segnale verrà eseguito con il blocco ancora detenuto. Sarà quindi attendere test_loop per uscire, e se questo thread è in attesa per la serratura, ti deadlock.

Si tratta di una intera categoria di problemi, ed è il motivo per cui un molto di API non dicono a loro chiamare dall'interno gestori di segnale. Invece, si dovrebbe impostare un flag per raccontare il filo conduttore di chiudere al momento opportuno.

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

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

La mia preferenza è quella di evitare di uscire dal programma con sys.exit interamente ed esplicitamente fare pulizia al punto di uscita principale (ad es. La fine del run ()), ma è possibile utilizzare atexit qui se vuoi.

Non sono sicuro se questo è stato interamente cambiato, ma questo è come ho il mio atexit fatto in 2.6.5


atexit.register(goodbye)

def goodbye():
    print "\nStopping..."
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top