|
1 | 1 | ============================
|
2 |
| -8.23 循环数据结构的内存管理 |
| 2 | +8.23 循环引用数据结构的内存管理 |
3 | 3 | ============================
|
4 | 4 |
|
5 | 5 | ----------
|
6 | 6 | 问题
|
7 | 7 | ----------
|
8 |
| -todo... |
| 8 | +你的程序创建了很多循环引用数据结构(比如树、图、观察者模式等),你碰到了内存管理难题。 |
| 9 | + |
| 10 | +| |
9 | 11 |
|
10 | 12 | ----------
|
11 | 13 | 解决方案
|
12 | 14 | ----------
|
13 |
| -todo... |
| 15 | +一个简单的循环引用数据结构例子就是一个树形结构,双亲节点有指针指向孩子节点,孩子节点又返回来指向双亲节点。 |
| 16 | +这种情况下,可以考虑使用 ``weakref`` 库中的弱引用。例如: |
| 17 | + |
| 18 | +.. code-block:: python |
| 19 | +
|
| 20 | + import weakref |
| 21 | +
|
| 22 | + class Node: |
| 23 | + def __init__(self, value): |
| 24 | + self.value = value |
| 25 | + self._parent = None |
| 26 | + self.children = [] |
| 27 | +
|
| 28 | + def __repr__(self): |
| 29 | + return 'Node({!r:})'.format(self.value) |
| 30 | +
|
| 31 | + # property that manages the parent as a weak-reference |
| 32 | + @property |
| 33 | + def parent(self): |
| 34 | + return None if self._parent is None else self._parent() |
| 35 | +
|
| 36 | + @parent.setter |
| 37 | + def parent(self, node): |
| 38 | + self._parent = weakref.ref(node) |
| 39 | +
|
| 40 | + def add_child(self, child): |
| 41 | + self.children.append(child) |
| 42 | + child.parent = self |
| 43 | +
|
| 44 | +这种是想方式允许parent静默终止。例如: |
| 45 | + |
| 46 | +.. code-block:: python |
| 47 | +
|
| 48 | + >>> root = Node('parent') |
| 49 | + >>> c1 = Node('child') |
| 50 | + >>> root.add_child(c1) |
| 51 | + >>> print(c1.parent) |
| 52 | + Node('parent') |
| 53 | + >>> del root |
| 54 | + >>> print(c1.parent) |
| 55 | + None |
| 56 | + >>> |
| 57 | +
|
| 58 | +| |
14 | 59 |
|
15 | 60 | ----------
|
16 | 61 | 讨论
|
17 | 62 | ----------
|
18 |
| -todo... |
| 63 | +循环引用的数据结构在Python中是一个很棘手的问题,因为正常的垃圾回收机制不能适用于这种情形。 |
| 64 | +例如考虑如下代码: |
| 65 | + |
| 66 | +.. code-block:: python |
| 67 | +
|
| 68 | + # Class just to illustrate when deletion occurs |
| 69 | + class Data: |
| 70 | + def __del__(self): |
| 71 | + print('Data.__del__') |
| 72 | +
|
| 73 | + # Node class involving a cycle |
| 74 | + class Node: |
| 75 | + def __init__(self): |
| 76 | + self.data = Data() |
| 77 | + self.parent = None |
| 78 | + self.children = [] |
| 79 | +
|
| 80 | + def add_child(self, child): |
| 81 | + self.children.append(child) |
| 82 | + child.parent = self |
| 83 | +
|
| 84 | +下面我们使用这个代码来做一些垃圾回收试验: |
| 85 | + |
| 86 | +.. code-block:: python |
| 87 | +
|
| 88 | + >>> a = Data() |
| 89 | + >>> del a # Immediately deleted |
| 90 | + Data.__del__ |
| 91 | + >>> a = Node() |
| 92 | + >>> del a # Immediately deleted |
| 93 | + Data.__del__ |
| 94 | + >>> a = Node() |
| 95 | + >>> a.add_child(Node()) |
| 96 | + >>> del a # Not deleted (no message) |
| 97 | + >>> |
| 98 | +
|
| 99 | +可以看到,最后一个的删除时打印语句没有出现。原因是Python的垃圾回收机制是基于简单的引用计数。 |
| 100 | +当一个对象的引用数变成0的时候才会立即删除掉。而对于循环引用这个条件永远不会成立。 |
| 101 | +因此,在上面例子中最后部分,父节点和孩子节点互相拥有对方的引用,导致每个对象的引用计数都不可能变成0。 |
| 102 | + |
| 103 | +Python有另外的垃圾回收器来专门针对循环引用的,但是你永远不知道它什么时候会触发。 |
| 104 | +另外你还可以手动的触发它,但是代码看上去很挫: |
| 105 | + |
| 106 | +.. code-block:: python |
| 107 | +
|
| 108 | + >>> import gc |
| 109 | + >>> gc.collect() # Force collection |
| 110 | + Data.__del__ |
| 111 | + Data.__del__ |
| 112 | + >>> |
| 113 | +
|
| 114 | +如果循环引用的对象自己还定义了自己的 ``__del__()`` 方法,那么会让情况变得更糟糕。 |
| 115 | +假设你像下面这样给Node定义自己的 ``__del__()`` 方法: |
| 116 | + |
| 117 | +.. code-block:: python |
| 118 | +
|
| 119 | + # Node class involving a cycle |
| 120 | + class Node: |
| 121 | + def __init__(self): |
| 122 | + self.data = Data() |
| 123 | + self.parent = None |
| 124 | + self.children = [] |
| 125 | +
|
| 126 | + def add_child(self, child): |
| 127 | + self.children.append(child) |
| 128 | + child.parent = self |
| 129 | +
|
| 130 | + # NEVER DEFINE LIKE THIS. |
| 131 | + # Only here to illustrate pathological behavior |
| 132 | + def __del__(self): |
| 133 | + del self.data |
| 134 | + del.parent |
| 135 | + del.children |
| 136 | +
|
| 137 | +这种情况下,垃圾回收永远都不会去回收这个对象的,还会导致内存泄露。 |
| 138 | +如果你试着去运行它会发现,``Data.__del__`` 消息永远不会出现了,甚至在你强制内存回收时: |
| 139 | + |
| 140 | +.. code-block:: python |
| 141 | +
|
| 142 | + >>> a = Node() |
| 143 | + >>> a.add_child(Node() |
| 144 | + >>> del a # No message (not collected) |
| 145 | + >>> import gc |
| 146 | + >>> gc.collect() # No message (not collected) |
| 147 | + >>> |
| 148 | +
|
| 149 | +弱引用消除了引用循环的这个问题,本质来讲,弱引用就是一个对象指针,它不会增加它的引用计数。 |
| 150 | +你可以通过 ``weakref`` 来创建弱引用。例如: |
| 151 | +
|
| 152 | +.. code-block:: python |
| 153 | +
|
| 154 | + >>> import weakref |
| 155 | + >>> a = Node() |
| 156 | + >>> a_ref = weakref.ref(a) |
| 157 | + >>> a_ref |
| 158 | + <weakref at 0x100581f70; to 'Node' at 0x1005c5410> |
| 159 | + >>> |
| 160 | +为了访问弱引用所引用的对象,你可以像函数一样去调用它即可。如果那个对象还存在就会返回它,否则就返回一个None。 |
| 161 | +由于原始对象的引用计数没有增加,那么就可以去删除它了。例如; |
| 162 | +
|
| 163 | +.. code-block:: python |
| 164 | +
|
| 165 | + >>> print(a_ref()) |
| 166 | + <__main__.Node object at 0x1005c5410> |
| 167 | + >>> del a |
| 168 | + Data.__del__ |
| 169 | + >>> print(a_ref()) |
| 170 | + None |
| 171 | + >>> |
| 172 | +
|
| 173 | +通过这里演示的弱引用技术,你会发现不再有循环引用问题了,一旦某个节点不被使用了,垃圾回收器立即回收它。 |
| 174 | +你还能参考8.25小节关于弱引用的另外一个例子。 |
0 commit comments