【问题标题】: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() 的调用只是退出该单独的实例?如果是这样,线程实现如何管理对共享资源的访问?如果我确实想从线程中退出程序(不是我真正想要的,只是我理解的那样)?

【问题讨论】:

    标签: python python-2.6


    【解决方案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 语句的另一个原因。

        【讨论】:

          猜你喜欢
          • 2019-01-22
          • 2010-11-15
          • 2013-10-13
          • 2014-06-10
          • 1970-01-01
          • 1970-01-01
          • 2014-03-17
          • 2018-09-11
          • 2016-12-12
          相关资源
          最近更新 更多