为什么在Python的线程内调用sys.exit()时不退出?

问题:为什么在Python的线程内调用sys.exit()时不退出?

这可能是一个愚蠢的问题,但是我正在测试我对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()的调用只是退出了该单独的实例?如果是这样,线程实现如何管理对共享资源的访问?如果我确实想从线程中退出程序怎么办(不是我真正想要的,但据我所知)?

This could be a stupid question, but I’m testing out some of my assumptions about Python and I’m confused as to why the following code snippet would not exit when called in the thread, but would exit when called in the main thread.

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"

The docs for sys.exit() state that the call should exit from Python. I can see from the output of this program that “post thread exit” is never printed, but the main thread just keeps on going even after the thread calls exit.

Is a separate instance of the interpreter being created for each thread, and the call to exit() is just exiting that separate instance? If so, how does the threading implementation manage access to shared resources? What if I did want to exit the program from the thread (not that I actually want to, but just so I understand)?


回答 0

sys.exit()和一样引发SystemExit异常thread.exit()。因此,当sys.exit()在该线程内引发该异常时,它的作用与调用相同thread.exit(),这就是为什么只有该线程退出的原因。

sys.exit() raises the SystemExit exception, as does thread.exit(). So, when sys.exit() raises that exception inside that thread, it has the same effect as calling thread.exit(), which is why only the thread exits.


回答 1

如果我确实想从线程中退出程序怎么办?

除了Deestan描述的方法之外,您还可以调用os._exit(注意下划线)。使用之前,请确保你明白,它没有清理(如呼叫__del__或类似)。

What if I did want to exit the program from the thread?

Apart from the method Deestan described you can call os._exit (notice the underscore). Before using it make sure that you understand that it does no cleanups (like calling __del__ or similar).


回答 2

如果我确实想从线程中退出程序怎么办(不是我真正想要的,但据我所知)?

至少在Linux上,您可以执行以下操作:

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

这会将a发送SIGINT到主线程,并引发a KeyboardInterrupt。这样您就可以进行适当的清理了。如果需要,您甚至可以处理信号。

顺便说一句:在Windows上,您只能发送SIGTERM信号,而Python无法捕获该信号。在这种情况下,您可以简单地使用os._exit相同的效果。

What if I did want to exit the program from the thread?

For Linux:

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

This sends a SIGINT to the main thread which raises a KeyboardInterrupt. With that you have a proper cleanup. Also you can register a handler, if you want to react differently.

The above does not work on Windows, as you can only send a SIGTERM signal, which is not handled by Python and has the same effect as sys._exit().

For Windows:

You can use:

sys._exit()

This will exit the entire process. However, without any cleanup. If you need that, you need to communicate with the main thread in another way.


回答 3

是“打印前主出口,线程后出口”的事实困扰着您吗?

与其他语言(如Java)不同,其他语言(如Java)的类似物sys.exitSystem.exit在Java中为Java)会导致VM /进程/解释器立即停止,而Python sys.exit只是抛出一个异常:特别是SystemExit异常。

以下是sys.exit(just print sys.exit.__doc__)的文档:

通过提高SystemExit(status)退出解释器。
如果省略状态或无,则默认为零(即成功)。
如果状态为数字,则将其用作系统退出状态。
如果是另一种对象,则将其打印出来,并且系统
退出状态将为1(即失败)。

这会带来一些后果:

  • 在一个线程中,它只是杀死当前线程,而不是杀死整个进程(假设它一直到达栈顶…)
  • __del__当引用那些对象的堆栈框架被展开时,可能会调用对象析构函数()
  • 最后,在堆栈展开时执行块
  • 你可以抓住一个SystemExitexceptions

最后一个可能是最令人惊讶的,也是另一个原因,为什么您几乎不应except在Python代码中使用不合格的语句。

Is the fact that “pre main exit, post thread exit” is printed what’s bothering you?

Unlike some other languages (like Java) where the analog to sys.exit (System.exit, in Java’s case) causes the VM/process/interpreter to immediately stop, Python’s sys.exit just throws an exception: a SystemExit exception in particular.

Here are the docs for sys.exit (just print sys.exit.__doc__):

Exit the interpreter by raising SystemExit(status).
If the status is omitted or None, it defaults to zero (i.e., success).
If the status is numeric, it will be used as the system exit status.
If it is another kind of object, it will be printed and the system
exit status will be one (i.e., failure).

This has a few consequences:

  • in a thread it just kills the current thread, not the entire process (assuming it gets all the way to the top of the stack…)
  • object destructors (__del__) are potentially invoked as the stack frames that reference those objects are unwound
  • finally blocks are executed as the stack unwinds
  • you can catch a SystemExit exception

The last is possibly the most surprising, and is yet another reason why you should almost never have an unqualified except statement in your Python code.


回答 4

如果我确实想从线程中退出程序怎么办(不是我真正想要的,但据我所知)?

我的首选方法是传递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

What if I did want to exit the program from the thread (not that I actually want to, but just so I understand)?

My preferred method is Erlang-ish message passing. Slightly simlified, I do it like this:

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