zshuangyan / blog

我的个人博客
2 stars 0 forks source link

Python垃圾回收:循环引用 #40

Open zshuangyan opened 5 years ago

zshuangyan commented 5 years ago

Python垃圾回收使用的是引用计数的方式,当一个对象不再有任何引用和它关联时,它的引用计数变为0,将会触发Python的垃圾回收系统对其进行回收。

但是如果遇到两个对象相互引用的情况,例如双向链表中的相邻两个节点,Python的垃圾回收系统就不能很好地工作了。

import gc, ctypes

def get_object(obj_id):
    return ctypes.cast(obj_id, ctypes.py_object).value

class Node:
    def __init__(self, val):
        self.val = val
        self.prev = None
        self.next = None

    def __repr__(self):
        return "Node(%s)" % self.val

first, second = Node(1), Node(2)
id_first, id_second = id(first), id(second)
first.next, second.prev = second, first

print("ref count of first node: %s" % len(gc.get_referrers(first)))
print("ref count of second node: %s" % len(gc.get_referrers(second)))
print("delete first node")
del first
print("ref count of first node: %s" % len(gc.get_referrers(get_object(id_first))))
print("delete second node")
del second
print("ref count of first node: %s" % len(gc.get_referrers(get_object(id_first))))
print("ref count of second node: %s" % len(gc.get_referrers(get_object(id_second))))

输出结果

ref count of first node: 2
ref count of second node: 2
delete first node
ref count of first node: 1
delete second node
ref count of first node: 1
ref count of second node: 1

删除first后,first的引用计数为1,因为此时second.prev指向了first,first对应的对象无法被回收 删除second后,second的引用计数为1,因为此时first对应的对象的next属性指向second

随着程序的执行,越来越多的循环引用占用的内存无法释放,将可能导致内存泄漏~

解决方法一

Python有另外的垃圾回收器来专门针对循环引用的,但是你永远不知道它什么时候会触发。 你还可以手动的触发它:

print("execute garbage collecting")
gc.collect()
print("ref count of first node: %s" % len(gc.get_referrers(get_object(id_first))))
print("ref count of second node: %s" % len(gc.get_referrers(get_object(id_second))))

执行结果:

execute garbage collecting
ref count of first node: 0
ref count of second node: 0

解决方法二

import gc, ctypes, weakref

def get_object(obj_id):
    return ctypes.cast(obj_id, ctypes.py_object).value

class Node:
    def __init__(self, val):
        self.val = val
        self.prev = None
        self.next = None

    def __repr__(self):
        return "Node(%s)" % self.val

first, second = Node(1), Node(2)
id_first, id_second = id(first), id(second)
first.next, second.prev = weakref.ref(second), weakref.ref(first)

print("ref count of first node: %s" % len(gc.get_referrers(first)))
print("ref count of second node: %s" % len(gc.get_referrers(second)))
print("delete first node")
del first
print("ref count of first node: %s" % len(gc.get_referrers(get_object(id_first))))
print("delete second node")
del second
print("ref count of first node: %s" % len(gc.get_referrers(get_object(id_first))))
print("ref count of second node: %s" % len(gc.get_referrers(get_object(id_second))))

执行结果:

ref count of first node: 1
ref count of second node: 1
delete first node
ref count of first node: 0
delete second node
ref count of first node: 0
ref count of second node: 0