You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python异常场景下垃圾回收死锁问题及最佳实践咨询

Python中__del__方法因引用未释放导致线程无法退出的问题解析与解决方案

原因解析

异常场景

当main()函数抛出RuntimeError时,Python会生成包含栈帧信息的异常追踪(traceback),栈帧中保留了局部变量a的引用。只要该traceback未被销毁(未捕获或被保留),A对象就无法被垃圾回收,__del__方法不会执行。子线程会持续等待self._event,而主线程因未处理的异常尝试退出时,会被非守护状态的子线程阻塞,最终导致程序永久挂起。

全局变量场景

执行A()时,创建的是无引用的临时对象,会立即被垃圾回收,__del__方法触发后设置事件并等待子线程退出,程序正常结束。但执行a = A()时,对象被绑定到全局变量a,Python程序退出时,全局变量的销毁顺序不确定,且解释器在退出阶段不会主动触发垃圾回收销毁全局变量。子线程作为非守护线程持续运行,__del__方法因引用未释放迟迟不执行,事件无法被设置,最终程序挂起。即使解释器后期销毁全局变量,此时线程相关资源可能已被部分回收,导致__del__中的操作无法正常生效。

是否为Python 3.10的Bug?

这不是Bug,是Python内存管理与异常处理机制的正常行为:

  • 异常追踪保留栈帧变量引用是为了提供完整调试信息;
  • 全局变量生命周期持续到程序退出,解释器不会提前销毁;
  • __del__方法的执行时机不可控,Python不保证其一定会被调用,尤其是程序退出阶段。

最佳实践

1. 用上下文管理器替代__del__管理资源

__del__执行时机不可控,推荐使用with语句实现的上下文管理器,确保无论是否发生异常,资源都能被显式清理:

import threading

class A:
    def __init__(self):
        self._event = threading.Event()
        self._thread = threading.Thread(target=self._event.wait)
        self._thread.start()

    def close(self):
        print('close')
        self._event.set()
        self._thread.join()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

def main():
    with A():
        raise RuntimeError()

main()

2. 使用守护线程

如果子线程不需要等待程序完成所有任务,可将其设置为守护线程,主线程退出时会强制终止守护线程,避免挂起:

# 修改A类的__init__方法
def __init__(self):
    self._event = threading.Event()
    self._thread = threading.Thread(target=self._event.wait, daemon=True)
    self._thread.start()

3. 显式调用资源清理方法

在可能抛出异常的代码块中,通过try/finally显式调用资源清理方法,不依赖__del__:

def main():
    a = A()
    try:
        raise RuntimeError()
    finally:
        # 无论是否异常,都显式关闭资源
        print('del')
        a._event.set()
        a._thread.join()

4. 避免全局变量持有资源对象

全局变量生命周期长,易导致资源无法及时释放,尽量使用局部变量配合上下文管理器或显式清理逻辑。

内容的提问来源于stack exchange,提问作者xpilot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 18:10:36