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


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退出。从该程序的输出中可以看到,“ post thread exit”从不打印,但即使在线程调用退出之后,主线程仍继续运行。

是否为每个线程创建了一个单独的解释器实例,并且对exit()的调用只是退出了该单独的实例?如果是这样,线程实现如何管理对共享资源的访问?如果我确实想从线程中退出程序怎么办(不是我真正想要的,但据我所知)?

Answers:


71

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


23

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

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


2
它会刷新I / O吗?
洛伦佐·贝利

1
os._exit(n):“退出状态为n的进程,而无需调用清理处理程序,刷新stdio缓冲区等。”
蒂姆·理查森

请注意,os._exit在curses中使用when时,控制台不会因此重置为正常状态。您必须reset在Unix-shell中执行才能解决此问题。
sjngm

22

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

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

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

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

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


1
也可用于不同于os._exit的
sjngm

12

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

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

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

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

这会带来一些后果:

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

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


11

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

我的首选方法是传递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
您不需要Queue这里。只需简单即可bool。此变量的经典名称为is_active,其初始默认值为True
Acumenus

3
是的,你是对的。根据effbot.org/zone/thread-synchronization.htm的说明,修改一个bool(或任何其他原子操作)将可以完美解决此特定问题。我之所以去Queues是与螺纹代理工作时,我往往最终需要几个不同的信号(flushreconnectexit,等...),几乎立即。
Deestan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.