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

Posted

技术标签:

【中文标题】为啥 sys.exit() 在 Python 的线程内调用时不退出?【英文标题】:Why does sys.exit() not exit when called inside a thread in Python?为什么 sys.exit() 在 Python 的线程内调用时不退出? 【发布时间】:2010-10-28 15:47:52 【问题描述】:

这可能是一个愚蠢的问题,但我正在测试我对 Python 的一些假设,我很困惑为什么以下代码 sn-p 在线程中调用时不会退出,但在调用时会退出在主线程中。

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 之后,主线程也会继续运行。

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

【问题讨论】:

【参考方案1】:

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

对于 Linux:

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

这会将SIGINT 发送到引发KeyboardInterrupt 的主线程。这样你就有了适当的清理工作。如果您想做出不同的反应,也可以注册一个处理程序。

以上在 Windows 上是行不通的,因为你只能发送一个SIGTERM 信号,它不被 Python 处理,和os._exit() 的效果一样。

对于 Windows:

你可以使用:

os._exit()

这将退出整个过程而不进行任何清理。如果需要清理,则需要以另一种方式与主线程通信。

【讨论】:

也适用于与 os._exit 不同的诅咒 module 'sys' has no attribute '_exit'。 Windows 10,Python 3.7.9 你说得对,应该是os._exit。现在改了。【参考方案2】:

_thread.interrupt_main() 从 Python 3.7 开始可用(在此之前可选)

【讨论】:

【参考方案3】:

sys.exit() 引发 SystemExit 异常,thread.exit() 也是如此。因此,当sys.exit() 在该线程内引发该异常时,它与调用thread.exit() 具有相同的效果,这就是只有线程退出的原因。

【讨论】:

【参考方案4】:

如果我确实想退出程序怎么办 来自线程?

除了 Deestan 描述的方法之外,您还可以调用 os._exit(注意下划线)。在使用它之前,请确保您了解它不会进行清理(例如调用__del__ 或类似的方法)。

【讨论】:

它会刷新 I/O 吗? os._exit(n): "以状态 n 退出进程,不调用清理处理程序,刷新 stdio 缓冲区等。" 请注意,当 os._exit 在 curses 中使用时,控制台不会因此重置为正常状态。你必须在 Unix-shell 中执行 reset 来解决这个问题。【参考方案5】:

如果我确实想退出程序怎么办 从线程(不是我实际上 想要,但我只是这么理解)?

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

【讨论】:

这里不需要Queue。只需一个简单的bool 就可以了。此变量的经典名称为is_active,其初始默认值为True 是的,你是对的。根据 effbot.org/zone/thread-synchronization.htm ,修改 bool (或任何其他原子操作)将完美解决这个特定问题。我选择Queues 的原因是,在使用线程代理时,我往往会立即需要几个不同的信号(flushreconnectexit 等...)。 Deestan:正如@Acumenus 指出的那样,既然bool 可以工作,那么同样一个简单的int 似乎就足以处理几个不同的信号——bool 是毕竟只是int 的一个子类。【参考方案6】:

打印“pre main exit, post thread exit”这个事实让你烦恼吗?

与其他一些语言(如 Java)不同,其中 sys.exit 的模拟(System.exit,在 Java 的情况下)会导致 VM/进程/解释器立即停止,Python 的 sys.exit 只是抛出一个异常:SystemExit 异常特别是。

这里是sys.exit(只是print sys.exit.__doc__)的文档:

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

这有几个后果:

在线程中它只是杀死当前线程,而不是整个进程(假设它一直到达堆栈的顶部......) 对象析构函数 (__del__) 可能会在引用这些对象的堆栈帧展开时被调用 最终块在堆栈展开时执行 您可以捕获SystemExit 异常

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

【讨论】:

以上是关于为啥 sys.exit() 在 Python 的线程内调用时不退出?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 def main(argv=[__name__]) 和 if __name__ == "__main__": sys.exit(main(sys.argv))?

Python3.x:os._exit(), sys.exit(), exit() 的区别

Python os._exit() sys.exit()

python:sys.exit() os._exit() exit() quit()

我想知道 sys.exit(-1) 在 python 中究竟返回了啥?

python中使用sys模块中sys.exit()好像不能退出?