__del__没有在对象的最后一个引用被删除时隐式调用



我有一个类,启动一个线程在其__init__成员,并且我想在不再需要该类的实例时加入该线程,因此我在__del__中实现了清理代码.

结果__del__成员在删除实例的最后一个引用时永远不会被调用,但是如果我隐式调用del,它被调用。

下面是我的实现的一个简短的修改版本,它显示了这个问题。

import sys
from queue import Queue
from threading import Thread
class Manager:
def __init__(self):
''' 
Constructor. 
'''
# Queue storing the incoming messages.
self._message_q = Queue()
# Thread de-queuing the messages.
self._message_thread = 
Thread(target=process_messages, args=(self._message_q,))
# Start the processing messages thread to consume the message queue.
self._message_thread.start()
def __del__(self):
''' 
Destructor. Terminates and joins the instance's thread.
'''
print("clean-up.")
# Terminate the consumer thread.
# - Signal the thread to stop.
self._message_q.put(None)
# - Join the thread.
self._message_thread.join()
def process_messages( message_q):
''' 
Consumes the message queue and passes each message to each registered
observer.
'''
while True:
print("got in the infinite loop")
msg = message_q.get()
print("got a msg")
if msg is None:
# Terminate the thread.
print("exit the loop.")
break
# Do something with message here.
mgr = Manager()
print("mgr ref count:" + str(sys.getrefcount(mgr) - 1)) # -1 cause the ref passed to getrefcount is copied. 
#del mgr

控制台输出以下代码:

mgr ref count:1
got in th infinite loop

执行挂起,因为线程仍在运行。由于某些原因,我不理解__del__,因此线程不会被终止。

如果取消最后一行del mgr的注释以显式删除实例,则__del__被调用并进行线程清理。

mgr ref count:1
clean-up.
got in the infinite loop
got a msg
exit the loop.
Press any key to continue . . .

有人能解释一下吗?

Silvio的回答是正确的,但不完整。实际上,保证了在这种情况下mgr不会被删除,因为:

  1. 消息线程不会退出,直到mgr的删除器被调用,
  2. 主线程在所有非守护线程完成后才开始清除模块全局变量的进程关闭部分。

这就产生了一个循环问题:

  1. 直到mgr被销毁,线程才会完成
  2. mgr不会被销毁,直到线程完成

这里的显式del mgr有效,假设没有其他对mgr的引用存在(隐式或显式)。你可以通过将代码放入函数中来获得更安全,更自动的清理版本,例如,标准设计(通过使用基于函数数组的局部变量替换基于dict的全局变量,实际上使事情运行得更快)是将主要功能放入函数中,然后调用它:

def main():
mgr = Manager()
print("mgr ref count:" + str(sys.getrefcount(mgr) - 1)) # -1 cause the ref passed to getrefcount is copied. 
if __name__ == '__main__':
main()

但它仍然不完美;在main退出后,异常可能最终保留frame对象,导致不确定性清理(类似地,只有CPython引用解释器使用引用计数作为其主要GC机制;在其他Python解释器上清理是不确定的)。要使这个完全确定,唯一的方法是使你的对象成为一个上下文管理器,并使用with语句,例如:

import sys
from queue import Queue
from threading import Thread
class Manager:
def __init__(self):
# unchanged, except for one added line:
self._closed = False  # Flag to prevent double-close
# Give named version for rare cases where cleanup must be triggered
# manually in some other function and therefore with statements can't be used
def close(self):
'''Signal thread to end and wait for it to complete'''
if not self._closed:
self._closed = True
print("clean-up.")
# Terminate the consumer thread.
# - Signal the thread to stop.
self._message_q.put(None)
# - Join the thread.
self._message_thread.join()
__del__ = close  # Call it for users on best-effort basis if they forget to use with
def __enter__(self):
return self    # Nothing special to do on beginning with block
def __exit__(self, exc, obj, tb):
self.close()  # Perform cleanup on exiting with block
def process_messages( message_q):
# unchanged
with Manager() as mgr:
print("mgr ref count:" + str(sys.getrefcount(mgr) - 1)) # -1 cause the ref passed to getrefcount is copied. 
# Guaranteed cleanup of mgr here

来自__del__的官方文档

不能保证在解释器退出时仍然存在的对象调用__del__()方法。

您在模块级有一个名为mgr的对象引用。该引用在程序终止时存在,因此__del__可能被调用,也可能不被调用。


详细说明有关该问题的一些评论,__del__不应该被认为是资源释放器。也就是说,如果你是从c++来的,__del__而不是析构函数的等效物。__del__可能运行,也可能不运行(例如,如上所述,它不会在程序退出时运行),即使它运行,也可能比您预期的要晚得多,这取决于垃圾收集器对您的感觉。

如果您正在寻找在声明时释放的资源分配,那么您需要一个上下文管理器。

我不知道你确切的用例,但如果你想创建一个mgr对象,在特定的代码的末尾被释放,你可以写像

这样的东西
class Manager:
def __enter__(self):
# Resource allocation goes here
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# Resource deallocation goes here; you
# can use the arguments to determine
# if an exception was thrown in the 'with'
# block
pass
with Manager(...) as mgr:
# __enter__ is called here
... some code ...
# __exit__ is called at the end of this
# block, EVEN IF an exception is thrown.

最新更新