Почему sys.exit() не вызывается при вызове внутри потока в Python?

Это может быть глупый вопрос, но я тестирую некоторые из моих предположений о Python, и я смущен тем, почему следующий фрагмент кода не будет выходить при вызове в потоке, но будет завершен при вызове в основной поток.

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"

Документы для sys.exit() указывают, что вызов должен выйти из Python. Я вижу на выходе этой программы, что "post thread exit" никогда не печатается, но основной поток просто продолжается даже после того, как поток вызывает exit.

Является ли отдельный экземпляр создаваемого интерпретатора для каждого потока, а вызов exit() просто выходит из этого отдельного экземпляра? Если да, то каким образом реализация потоковой передачи управляет доступом к общим ресурсам? Что делать, если я действительно хочу выйти из программы из потока (не то, что я на самом деле хочу, но именно так понимаю)?

Ответ 1

sys.exit() вызывает исключение SystemExit, как и thread.exit(). Таким образом, когда sys.exit() вызывает это исключение внутри этого потока, он имеет тот же эффект, что и вызов thread.exit(), поэтому выходит только поток.

Ответ 2

Что делать, если я хочу выйти из программы? из потока (не то, что я на самом деле хочу, но просто так понимаю)?

Мой предпочтительный метод - передача сообщений Erlang-ish. Немного смягченный, я делаю это так:

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

Что делать, если я хочу выйти из программы из потока (не то, что я на самом деле хочу, но просто так понимаю)?

По крайней мере, на Linux вы можете сделать что-то вроде:

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

Это отправляет SIGINT в основной поток, где его можно обрабатывать как KeyboardInterrupt.

BTW: В Windows вы можете отправлять только сигнал SIGTERM, который нельзя поймать с Python. (там я бы предпочел называть os._exit, как предложил Хельмут)

Ответ 4

Что делать, если я хочу выйти из программы? из потока?

Помимо метода, описанного Deestan, вы можете вызвать os._exit (обратите внимание на подчеркивание). Перед использованием убедитесь, что вы понимаете, что он не очищает (например, вызов __del__ или аналогичный).

Ответ 5

Является ли тот факт, что "pre main exit, post thread exit" напечатан, что вас беспокоит?

В отличие от некоторых других языков (например, Java), где аналог sys.exit (System.exit, в случае Java) заставляет VM/process/interpreter немедленно останавливаться, Python sys.exit просто генерирует исключение: исключение SystemExit в частности.

Вот документы для sys.exit (просто print sys.exit.__doc__):

Выйдите из интерпретатора, подняв SystemExit (статус).
Если статус опущен или None, по умолчанию он равен нулю (то есть, успех).
Если статус является числовым, он будет использоваться как статус выхода системы.
Если это другой вид объекта, он будет напечатан, а система - статус выхода будет одним (то есть сбой).

Это имеет несколько последствий:

  • в потоке он просто убивает текущий поток, а не весь процесс (при условии, что он доходит до вершины стека...)
  • деструкторы объектов (__del__) потенциально вызывается, поскольку фреймы стека, которые ссылаются на эти объекты, разматываются
  • finally блоки выполняются по мере удаления пакета
  • вы можете поймать исключение SystemExit

Последнее, возможно, самое удивительное, и это еще одна причина, по которой у вас почти никогда не должно быть безусловного оператора except в вашем коде Python.