终止python程序的正确方法,无论调用位置如何,并进行清理

Posted

技术标签:

【中文标题】终止python程序的正确方法,无论调用位置如何,并进行清理【英文标题】:Proper method for terminating a python program regardless of call location, with cleanup 【发布时间】:2018-08-25 19:05:53 【问题描述】:

如果需要,如何正确、干净地终止 Python 程序? sys.exit() 不能可靠地执行此功能,因为它只是终止调用它的线程,exit()quit() 不应该在终端窗口中使用,raise SystemExitsys.exit() 和是不好的做法,os._exit() 会立即杀死所有东西并且不清理,这可能会导致残留问题。

有没有办法总是杀死程序和所有线程,不管它是从哪里调用的,同时仍然清理?

【问题讨论】:

您可以使用atexit 来定义清理操作。 另见Python exit commands - why so many and when should each be used?。 如何在您的课程中使用“上下文管理协议”,您可以在其中定义__enter____exit__ 并使用with context 运行您的代码 @a_guest 没有解决sys.ext() 和线程的问题。我不得不问这个问题的全部原因是因为这种行为使它太不可靠了,特别是因为许多解释器会将一些调用分解为不可见的线程,导致它在非主线程中被调用,即使你不是故意使用线程。跨度> @Elliot 我知道,这就是为什么我没有将您的问题标记为重复的原因。我只是想将其他读者指向该资源。 【参考方案1】:

有没有办法总是杀死程序和所有线程,不管它是从哪里调用的,同时仍然清理?

否 - “无论从哪里调用”和“清理”不要混用。


可靠又安全地杀死一个线程根本没有意义。杀死一个线程(或进程)意味着中断它正在做的事情——包括清理。不中断任何清理意味着,实际上并没有杀死线程。你不能同时拥有两者。

如果您想杀死所有线程,那么os._exit() 正是您所要求的。如果你想清理线程,没有通用功能可以实现。

关闭线程的唯一可靠方法是实现您自己的安全中断。在某种程度上,这必须根据您的用例进行定制 - 毕竟,您是唯一知道何时可以安全关闭的人。

用异常终止线程

底层 CPython API 允许您在另一个线程中引发异常。参见例如this answer。

这不是便携式的,不安全。您可以在任意点杀死该线程。如果您的代码预期异常或您的资源自行清理(通过__del__),您可以限制危害,但不能排除它。尽管如此,它仍然非常接近大多数人认为的“干净的杀戮”。

带有atexit 的自清洁守护线程

如果没有其他线程剩余,使用Thread.daemon 运行的线程会突然终止。通常,这是您想要的一半:优雅地如果所有适当的线程都退出,则终止。

现在,关键是daemon 线程不会阻止关机。这也意味着它不会阻止atexit 运行!因此,守护进程可以使用atexit 自动关闭自身并在终止时进行清理。

import threading
import atexit
import time


class CleanThread(threading.Thread):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.daemon = True
        # use a signal to inform running code about shutdown
        self.shutdown = threading.Event()

    def atexit_abort(self):
        # signal to the thread to shutdown with the interpreter
        self.shutdown.set()
        # Thread.join in atexit causes interpreter shutdown
        # to be delayed until cleanup is done
        self.join()  # or just release resources and exit

    def run(self):
        atexit.register(self.atexit_abort)
        while not self.shutdown.wait(0.1):
            print('who wants to live forever?')
        print('not me!')
        atexit.unregister(self.atexit_abort)


thread = CleanThread()
thread.start()
time.sleep(0.3)
# program exits here

请注意,这仍然需要您的代码来监听清理信号!根据线程的作用,还有其他机制可以实现这一点。例如,concurrent.future 模块在关闭时清空所有工作线程的任务队列。

【讨论】:

【参考方案2】:

当您的应用运行时,可能会发生坏事,我们希望从中恢复。一个例子是电源故障。

没有用于安排指令在断电设备上执行的计算技术。所以我们可能需要在重启时重置一些状态。你的应用已经有这个要求;我只是说清楚。

正如您在仔细考虑几种标准技术时发现的那样,在可能发生的各种坏事之后,很难可靠地获得控制权。您没有具体说明您设想的需要清理的项目类型,但我们可以考虑以下情况:

    transient - TCP 连接、flock 等 永久 - 磁盘文件,对远程主机的副作用

与其直接调用您的应用程序,不如安排它由 Nanny 进程运行,该进程将应用程序作为一个子进程进行分叉。在某些时候应用程序将退出,Nanny 将重新获得控制权,所有临时项目都已被操作系统清理,然后 Nanny 可以在应用程序重新启动之前对永久项目进行任何必要的清理。这与 Nanny 在初始启动时需要执行的清理操作相同,例如在电源故障事件之后。在父进程下运行您的应用的优势在于,父进程可以在 SEGV 等简单的应用故障后立即执行清理。

清理永久项目可能涉及时间戳资源超时。如果您的系统能够在短暂断电后 2 秒内重新启动,您可能会发现有必要在宣布转换之前故意保持 Down(睡眠)足够长的时间,以确保远程主机可靠地检测到您的转换为 Down向上。 Virtual Synchrony 和 Paxos 等技术可以帮助您实现快速收敛。

总结

有时应用会在运行清理代码之前意外死机。采取一种带吊带的方法:将基本的清理代码放在(更简单、更可靠的)父进程中。

【讨论】:

以上是关于终止python程序的正确方法,无论调用位置如何,并进行清理的主要内容,如果未能解决你的问题,请参考以下文章

如何在 iOS 中以终止/终止模式调用 API

如何正确的终止正在运行的子线程

正确终止程序。使用异常

delphi 如何在自动终止线程和手动终止线程时都正确的设置窗体上的控件

在鼠标单击方法中,当我调用方法获取 X() 并获取 Y() 时,无论位置如何,它都会返回 -8 [关闭]

当应用程序处于后台或终止状态时,如何找到用户位置?