Dlaczego sys.exit () nie kończy pracy, gdy jest wywoływana wewnątrz wątku w Pythonie?

101

Może to być głupie pytanie, ale testuję niektóre z moich założeń dotyczących Pythona i nie wiem, dlaczego następujący fragment kodu nie kończy się po wywołaniu w wątku, ale kończy się po wywołaniu w wątku głównym.

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"

Dokumentacja sys.exit () stwierdza, że ​​wywołanie powinno zakończyć się z Pythona. Widzę na wyjściu tego programu, że „post wątek wyjścia” nigdy nie jest drukowany, ale główny wątek po prostu działa nawet po wywołaniu wątku wyjścia.

Czy dla każdego wątku tworzona jest osobna instancja interpretera, a wywołanie exit () właśnie kończy pracę z tej oddzielnej instancji? Jeśli tak, w jaki sposób implementacja obsługi wątków zarządza dostępem do współdzielonych zasobów? A co jeśli chciałbym wyjść z programu z wątku (nie to, że tak naprawdę chcę, ale tylko po to, żeby zrozumieć)?

Shabbyrobe
źródło

Odpowiedzi:

74

sys.exit()podnosi SystemExitwyjątek, podobnie jak thread.exit(). Tak więc, gdy sys.exit()zgłasza ten wyjątek w tym wątku, ma to taki sam efekt jak wywołanie thread.exit(), dlatego tylko wątek kończy działanie.

rpkelly
źródło
25

A co jeśli chciałbym wyjść z programu z wątku?

Oprócz metody opisanej przez Deestana możesz zadzwonić os._exit(uwaga na podkreślenie). Przed użyciem upewnij się, że wiesz, że to nie ma cleanups (jak dzwoni __del__lub podobny).

Helmut Grohne
źródło
2
Czy opróżni I / O?
Lorenzo Belli
2
os._exit (n): "Zakończ proces ze statusem n, bez wywoływania programów obsługi czyszczenia, opróżniania buforów stdio itp."
Tim Richardson
Zauważ, że gdy os._exitjest używany w curses, konsola nie jest resetowana do normalnego stanu z tym. resetAby to naprawić, musisz wykonać w powłoce Uniksa.
sjngm
25

A co jeśli chciałbym wyjść z programu z wątku?

W systemie Linux:

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

To wysyła SIGINTdo głównego wątku, który podnosi KeyboardInterrupt. Dzięki temu masz porządne porządki. Możesz także zarejestrować handlera, jeśli chcesz inaczej zareagować.

Powyższe nie działa w systemie Windows, ponieważ można wysłać tylko SIGTERMsygnał, który nie jest obsługiwany przez Pythona i ma taki sam efekt jak sys._exit().

Dla Windowsa:

Po prostu użyj:

sys._exit()

Spowoduje to zamknięcie całego procesu. Jednak bez czyszczenia. Jeśli tego potrzebujesz, musisz komunikować się z głównym wątkiem w inny sposób.

Chris
źródło
2
Działa również z przekleństwami w przeciwieństwie do os._exit
sjngm
12

Czy to, że jest drukowane „przed wyjściem głównym, wyjście z wątku po”, cię niepokoi?

W przeciwieństwie do niektórych innych języków (takich jak Java), w których odpowiednik sys.exit( System.exitw przypadku Javy) powoduje natychmiastowe zatrzymanie maszyny wirtualnej / procesu / interpretera, język Python po sys.exitprostu zgłasza wyjątek: w szczególności wyjątek SystemExit.

Oto dokumenty dla sys.exit(tylko print sys.exit.__doc__):

Wyjdź z interpretera, podnosząc SystemExit (status).
Jeśli status zostanie pominięty lub Brak, przyjmuje wartość domyślną zero (tj. Sukces).
Jeśli status jest numeryczny, będzie używany jako status wyjścia z systemu.
Jeśli jest to obiekt innego rodzaju, zostanie wydrukowany, a kod
wyjścia z systemu będzie równy jeden (tj. Niepowodzenie).

Ma to kilka konsekwencji:

  • w wątku po prostu zabija bieżący wątek, a nie cały proces (zakładając, że dociera na sam szczyt stosu ...)
  • destruktory obiektów ( __del__) są potencjalnie wywoływane, gdy ramki stosu odwołujące się do tych obiektów są rozwijane
  • w końcu bloki są wykonywane, gdy stos się rozwija
  • możesz złapać SystemExitwyjątek

To ostatnie jest prawdopodobnie najbardziej zaskakujące i jest kolejnym powodem, dla którego prawie nigdy nie powinieneś mieć niekwalifikowanej exceptinstrukcji w kodzie Pythona.

Laurence Gonsalves
źródło
12

A co jeśli chciałbym wyjść z programu z wątku (nie to, że tak naprawdę chcę, ale tylko po to, żeby zrozumieć)?

Moją preferowaną metodą jest przekazywanie wiadomości Erlang-ish. Nieco uproszczone, robię to tak:

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
źródło
4
Nie potrzebujesz Queuetutaj. Wystarczy prosty boolzabieg. Klasyczna nazwa tej zmiennej to, is_activea jej początkowa wartość domyślna to True.
Acumenus,
4
Tak, masz rację. Według effbot.org/zone/thread-synchronization.htm , modyfikacja bool(lub jakiejkolwiek innej operacji atomowej) będzie idealnie pasować do tego konkretnego problemu. Powodem idę z Queues jest to, że podczas pracy z czynnikami gwintowanych staram się skończyć potrzebuje kilku różnych sygnałów ( flush, reconnect, exit, etc ...) niemal natychmiast.
Deestan
1
Deestan: Ponieważ a booldziałałoby, jak zauważył @Acumenus, to podobnie intwydaje się, że proste byłoby wszystko, co jest potrzebne do obsługi kilku różnych sygnałów - boolw intkońcu jest tylko podklasą .
martineau