Perché sys.exit () non esce quando viene chiamato all'interno di un thread in Python?


98

Questa potrebbe essere una domanda stupida, ma sto testando alcune delle mie ipotesi su Python e sono confuso sul motivo per cui il seguente frammento di codice non uscirà quando viene chiamato nel thread, ma uscirà quando viene chiamato nel thread principale.

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"

La documentazione di sys.exit () afferma che la chiamata dovrebbe uscire da Python. Vedo dall'output di questo programma che "post thread exit" non viene mai stampato, ma il thread principale continua a funzionare anche dopo che il thread chiama exit.

Viene creata un'istanza separata dell'interprete per ogni thread e la chiamata a exit () sta semplicemente uscendo da quell'istanza separata? In tal caso, in che modo l'implementazione del threading gestisce l'accesso alle risorse condivise? E se volessi uscire dal programma dal thread (non che lo voglia effettivamente, ma solo così ho capito)?

Risposte:


71

sys.exit()solleva l' SystemExiteccezione, così come thread.exit(). Quindi, quando sys.exit()solleva quell'eccezione all'interno di quel thread, ha lo stesso effetto della chiamata thread.exit(), motivo per cui esce solo il thread.


23

E se volessi uscire dal programma dal thread?

A parte il metodo descritto da Deestan puoi chiamare os._exit(notare il trattino basso). Prima di utilizzarlo assicurarsi che si capisce che lo fa non ripuliture (come la chiamata __del__o simili).


2
Svuoterà l'I / O?
Lorenzo Belli

1
os._exit (n): "Esci dal processo con stato n, senza chiamare i gestori di pulizia, svuotare i buffer stdio, ecc."
Tim Richardson

Si noti che quando os._exitviene utilizzato all'interno di curses, la console non viene ripristinata a uno stato normale con questo. Devi eseguire resetnella shell Unix per risolvere questo problema.
sjngm

22

E se volessi uscire dal programma dal thread (non che lo voglia effettivamente, ma solo così ho capito)?

Almeno su Linux puoi fare:

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

Questo invia un SIGINTal thread principale che solleva a KeyboardInterrupt. Con quello hai una corretta pulizia. Puoi persino gestire il segnale se ne hai bisogno.

BTW: su Windows puoi solo inviare un SIGTERMsegnale, che non può essere catturato da Python. In tal caso puoi semplicemente usare os._exitcon lo stesso effetto.


1
Funziona anche con maledizioni a differenza di os._exit
sjngm

12

Il fatto che sia stampato "pre main exit, post thread exit" è ciò che ti preoccupa?

A differenza di altri linguaggi (come Java) in cui l'analogo a sys.exit( System.exit, nel caso di Java) fa sì che la VM / processo / interprete si interrompa immediatamente, Python'ssys.exit lancia solo un'eccezione: un'eccezione SystemExit in particolare.

Ecco i documenti per sys.exit(solo print sys.exit.__doc__):

Esci dall'interprete alzando SystemExit (status).
Se lo stato è omesso o Nessuno, il valore predefinito è zero (cioè successo).
Se lo stato è numerico, verrà utilizzato come stato di uscita del sistema.
Se si tratta di un altro tipo di oggetto, verrà stampato e lo
stato di uscita del sistema sarà uno (ovvero, errore).

Ciò ha alcune conseguenze:

  • in un thread uccide solo il thread corrente, non l'intero processo (supponendo che arrivi fino in cima allo stack ...)
  • i distruttori di oggetti ( __del__) vengono potenzialmente richiamati quando gli stack frame che fanno riferimento a tali oggetti vengono annullati
  • infine i blocchi vengono eseguiti mentre lo stack si svolge
  • puoi prendere SystemExitun'eccezione

L'ultimo è forse il più sorprendente, ed è ancora un altro motivo per cui non dovresti quasi mai avere un'istruzione non qualificata exceptnel tuo codice Python.


11

E se volessi uscire dal programma dal thread (non che lo voglia effettivamente, ma solo così ho capito)?

Il mio metodo preferito è il passaggio di messaggi in stile Erlang. Leggermente semplificato, lo faccio in questo modo:

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

3
Non hai bisogno di un Queuequi. Solo un semplice boolandrà bene. Il nome classico di questa variabile è is_activee il suo valore predefinito iniziale è True.
Acumenus

3
Sì hai ragione. Secondo effbot.org/zone/thread-synchronization.htm , la modifica di a bool(o qualsiasi altra operazione atomica) funzionerà perfettamente per questo particolare problema. Il motivo per cui andare con Queues è che quando si lavora con agenti filettati tendo a finire che necessitano di più segnali differenti ( flush, reconnect, exit, ecc ...) quasi subito.
Deestan
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.