当另一个线程处于活动状态时,为什么不在全局对象上调用 __del__?

Why is __del__ not called on a global object when another thread is active?

我正在开发一个与下面代码中所示结构类似的项目。我希望有一个对象在创建时打开一个线程并在对象被销毁时自动关闭它。当对象在函数中实例化时,这按预期工作,但是当对象在全局范围内创建时,__del__ 没有被调用,导致程序挂起。

import threading

def thread(event):
    event.wait()
    print("Terminating thread")

class ThreadManager:
    def __init__(self):
        self.event = threading.Event()
        self.thread = threading.Thread(target=thread, args=(self.event,))
        self.thread.start()

    def __del__(self):
        self.event.set()
        print("Event set")
        self.thread.join()

if __name__ == '__main__':
    print("Creating thread")
    manager = ThreadManager()
    #del manager

除非我明确删除 manager 对象,否则程序会挂起。我假设解释器正在等待删除全局对象,直到所有非守护线程都完成,从而导致死锁情况。

我的问题是,有人可以确认这一点并提供解决方法(我已阅读 this 页面,所以我不是在寻找使用 close() 函数或类似内容的解决方案,我只是想听听有关执行自动清理的替代方案的想法),或者反驳它并告诉我我做错了什么?

与 C++ 等语言不同,Python 不会在对象超出范围时立即销毁对象,这就是 __del__ 不可靠的原因。您可以在此处阅读更多相关信息:

至于解决方案,我认为这是上下文管理器的完美案例 (with):

>>> import threading
>>>
>>> def thread(event):
...     event.wait()
...     print("Terminating thread")
...
>>> class ThreadManager:
...     def __init__(self):
...             print("__init__ executed")
...             self.event = threading.Event()
...             self.thread = threading.Thread(target=thread, args=(self.event,))
...             self.thread.start()
...     def __enter__(self):
...             print("__enter__ executed")
...             return self
...     def __exit__(self, *args):
...             print("__exit__ executed")
...             self.event.set()
...             print("Event set")
...             self.thread.join()
...
>>> with ThreadManager() as thread_manager:
...     print(f"Within context manager, using {thread_manager}")
...
__init__ executed
__enter__ executed
Within context manager, using <__main__.ThreadManager object at 0x1049666d8>
__exit__ executed
Event set
Terminating thread

print 语句显示执行顺序按照您希望的方式工作,其中 __exit__ 现在是您可靠的清理方法。

这个行为其实在官方documentation中特别提到了object.__del__():

It is not guaranteed that __del__() methods are called for objects that still exist when the interpreter exits.