终止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 SystemExit
与 sys.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程序的正确方法,无论调用位置如何,并进行清理的主要内容,如果未能解决你的问题,请参考以下文章
delphi 如何在自动终止线程和手动终止线程时都正确的设置窗体上的控件