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

101

Это может быть глупый вопрос, но я проверяю некоторые из своих предположений о 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 () просто завершает этот отдельный экземпляр? Если да, то как реализация потоковой передачи управляет доступом к общим ресурсам? Что, если бы я действительно хотел выйти из программы из потока (не то чтобы я действительно этого хотел, а просто так, чтобы я понял)?

Шаббироб
источник

Ответы:

74

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

rpkelly
источник
25

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

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

Гельмут Гроне
источник
2
Будет ли он сбрасывать ввод / вывод?
Лоренцо Белли
2
os._exit (n): "Выйти из процесса со статусом n, без вызова обработчиков очистки, очистки буферов stdio и т. д."
Тим Ричардсон
Обратите внимание, что при os._exitиспользовании в curses консоль не возвращается в нормальное состояние. Вы должны выполнить это resetв Unix-оболочке, чтобы исправить это.
sjngm
25

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

Для Linux:

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

Это отправляет SIGINTв основной поток, который вызываетKeyboardInterrupt . С этим у вас есть надлежащая очистка. Также вы можете зарегистрировать обработчик, если хотите реагировать иначе.

Вышеупомянутое не работает в Windows, так как вы можете отправить только SIGTERMсигнал, который не обрабатывается Python и имеет тот же эффект, что и sys._exit().

Для Windows:

Ты можешь использовать:

sys._exit()

Это завершит весь процесс. Однако без какой-либо очистки. Если вам это нужно, вам нужно связаться с основным потоком другим способом.

Крис
источник
2
Также работает с проклятиями, в отличие от os._exit
sjngm
12

Вас беспокоит то, что печатается «pre main exit, post thread exit»?

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

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

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

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

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

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

Лоуренс Гонсалвес
источник
12

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

Мой предпочтительный метод - передача сообщений в стиле Erlang. Слегка упрощенно делаю так:

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
источник
4
Вам здесь не нужен Queue. Просто boolподойдет. Классическое имя для этой переменной is_activeи ее начальное значение по умолчанию True.
Acumenus
4
Да вы правы. Согласно effbot.org/zone/thread-synchronization.htm , изменение bool(или любой другой атомарной операции) отлично подойдет для этой конкретной проблемы. Причина я иду с Queueе в том , что при работе с резьбовыми агентами я , как правило, в конечном итоге нужно несколько различных сигналов ( flush, reconnect, exitи т.д. ...) почти сразу.
Deestan
1
Дистан: Поскольку a boolбудет работать, как указал @Acumenus, то int, похоже, простое решение - это все, что нужно для обработки нескольких различных сигналов - boolв intконце концов, это всего лишь подкласс .
Мартино,