Почему 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. Из результатов этой программы я вижу, что "выход из пост-потока" никогда не печатается, но основной поток просто продолжает работать даже после того, как поток вызывает выход.

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

6 ответов

Решение

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

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

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

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

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

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

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

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

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

Мой предпочтительный метод - передача сообщений 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

Тебя беспокоит тот факт, что "до главного выхода, после выхода из поста" напечатано?

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

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

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

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

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

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

_thread.interrupt_main() доступен начиная с Python 3.7 (до этого необязательно)

Другие вопросы по тегам