Чому sys.exit () не виходить при виклику всередині потоку в Python?


98

Це може бути дурним запитанням, але я перевіряю деякі свої припущення щодо 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 () просто виходить із цього окремого екземпляра? Якщо так, то як реалізація потоків управляє доступом до спільних ресурсів? Що, якби я хотів вийти з програми з потоку (не те, що я насправді хочу, але просто так, щоб я зрозумів)?

Відповіді:


71

sys.exit()викликає SystemExitвиняток, як це робить thread.exit(). Отже, коли sys.exit()викликає цей виняток усередині цього потоку, це має такий самий ефект, як виклик thread.exit(), саме тому виходить лише потік.


23

Що робити, якщо я хотів вийти з програми з потоку?

Окрім методу, описаного Деестаном, ви можете зателефонувати os._exit(зверніть увагу на підкреслення). Перед використанням його переконайтеся , що ви розумієте , що це не робить ні одного иборко (як виклик __del__або аналогічними).


2
Чи змиє введення / виведення?
Лоренцо Беллі

1
os._exit (n): "Вийти з процесу зі статусом n, не викликаючи обробників очищення, змиваючи буфери stdio тощо"
Тім Річардсон,

Зверніть увагу, що при os._exitвикористанні в межах проклять консоль не повертається до нормального стану за допомогою цього. Вам потрібно виконати resetв оболонці Unix, щоб це виправити.
sjngm

22

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

Принаймні в Linux ви можете зробити:

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

Це посилає a SIGINTдо основного потоку, який піднімає a KeyboardInterrupt. Завдяки цьому у вас буде належне очищення. Ви навіть можете обробити сигнал, якщо хочете.

До речі: у Windows ви можете надсилати лише SIGTERMсигнал, який неможливо перехопити з Python. У цьому випадку ви можете просто використовувати os._exitз тим же ефектом.


1
Також працює з прокляттями на відміну від os._exit
sjngm

12

Чи турбує той факт, що надруковано "перед основним виходом, вихід після потоку"?

На відміну від деяких інших мов (наприклад, Java), де аналог sys.exit( System.exitу випадку Java) викликає негайну зупинку VM / процес / інтерпретатор, Python sys.exitпросто видає виняток: зокрема виняток SystemExit.

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

Вийдіть з інтерпретатора, піднявши SystemExit (статус).
Якщо статус опущений або відсутній, значення за замовчуванням дорівнює нулю (тобто успіху).
Якщо статус є числовим, він буде використовуватися як статус виходу із системи.
Якщо це інший тип об'єкта, він буде надрукований, і
статус виходу із системи буде одним (тобто, збій).

Це має кілька наслідків:

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

Останнє, можливо, є найдивовижнішим, і це ще одна причина, через яку майже ніколи не слід мати некваліфікованого exceptвисловлення у коді Python.


11

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

Моїм улюбленим методом є передача 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

3
Вам тут не потрібно Queue. Просто простий boolпідійде добре. Класичне ім'я цієї змінної - is_activeце початкове значення за замовчуванням True.
Прозріння

3
Так, ти маєш рацію. Відповідно до effbot.org/zone/thread-synchronization.htm , зміна bool(або будь-якої іншої атомної операції) прекрасно підійде для цієї конкретної проблеми. Причина я йду з Queueе в тому , що при роботі з різьбовими агентами я , як правило, в кінцевому підсумку потрібно кілька різних сигналів ( flush, reconnect, exitі т.д. ...) майже відразу.
Deestan
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.