我遇到了一个奇怪的情况,由于python处理异常的方式,程序不会退出。在这种情况下,我有一个拥有一个线程的对象,并且该线程仅在调用该对象的
__del__
方法时才会关闭。但是,如果程序由于涉及该对象的异常而“退出”,则异常本身将在其堆栈跟踪中保存对该对象的引用,这会阻止该对象被删除。由于对象没有被删除,线程永远不会关闭,因此程序无法完全退出并永远挂起。这是一个小重现:
import threading
class A:
def __init__(self):
self._event = threading.Event()
self._thread = threading.Thread(target=self._event.wait)
self._thread.start()
def __del__(self):
print('del')
self._event.set()
self._thread.join()
def main():
a = A()
# The stack frame created here holds a reference to `a`, which
# can be verified by looking at `gc.get_referrers(a)` post-raise.
raise RuntimeError()
main() # hangs indefinitely
一种解决方法是通过消除异常并引发新异常来破坏引用链:
error = False
try:
main()
except RuntimeError as e:
error = True
if error:
# At this point the exception should be unreachable; however in some
# cases I've found it necessary to do a manual garbage collection.
import gc; gc.collect()
# Sadly this loses the stack trace, but that's what's necessary.
raise RuntimeError()
有趣的是,只要在主模块中留下对
a
的引用,就会出现类似的问题,没有任何例外:
A() # This is fine, prints 'del'
a = A() # hangs indefinitely
这是怎么回事?这是 python (3.10) 的错误吗?是否有避免此类问题的最佳实践?我真的花了很长时间才弄清楚发生了什么!
基于Python的数据模型:
不保证为对象调用
方法 当解释器退出时仍然存在。__del__()
所以你不应该在
__del__
函数中终止线程。相反,建议在适当的时候显式设置标志来发出终止信号,或者您可以使用上下文管理器:
import threading
class A:
def __init__(self):
self._event = threading.Event()
def __enter__(self):
self._thread = threading.Thread(target=self._event.wait)
self._thread.start()
def __exit__(self):
self._event.set()
self._thread.join()
def main():
with A() as a:
raise RuntimeError()
main()