为啥 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
(或任何其他原子操作)将完美解决这个特定问题。我选择Queue
s 的原因是,在使用线程代理时,我往往会立即需要几个不同的信号(flush
、reconnect
、exit
等...)。
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:sys.exit() os._exit() exit() quit()