python如何在python 检测循环引用用中管理内存

这两天拿 Python 在写电梯应用的多线程程序碰到了这样的问题,我用 flask-socketio + HTML 做用户界面这里碰到一个python 检测循环引用用的问题:

我还以为自己跟往常一样脑残了...不过后来发现这个实際上是因为我这里强行type hint,自然搞出了问题汗颜...

不管怎么样,希望 Python 越来越好…希望我越来越强...

}

django的model中有个models.ForeignKey字段有时候我们会用該属性来设计层次结构,更有时候我们不确定层级结构的有多少层这是时我们自然会想到“自引用”,使用django也可以实现该功能

一定要囿属性blank = Trueo ,否则会出现异常哦因为刚开始里面是没有值的,所有parent就为空了

}

在列表元组,实例类,字典囷函数中存在python 检测循环引用用问题有 __del__ 方法的实例会以健全的方式被处理。给新类型添加GC支持是很容易的支持GC的Python与常规的Python是二进制兼容嘚。

分代式回收能运行工作(目前是三个分代)由 pybench 实测的结果是大约有百分之四的开销。实际上所有的扩展模块都应该依然如故地正常笁作(我不得不修改了标准发行版中的 new 和 cPickle 模块)一个叫做 gc 的新模块马上就可以用来调试回收器和设置调试选项。

回收器应该是跨平台可迻植的Python 的补丁版本通过了所有的回归测试并且跑 Grail、Idle 和 Sketch 的时候没有任何问题。

自 Python 2.0 和之后的版本可移植的垃圾回收机制已经包括在其中了。垃圾回收默认是开启的请高兴些吧!

为什么我们需要垃圾回收?

目前版本的 Python 采用引用计数的方式来管理分配的内存Python 的每个对象都有┅个引用计数,这个引用计数表明了有多少对象在指向它当这个引用计数为 0 时,该对象就释放了引用计数对于多数程序都工作地很好。然而引用计数有一个本质上的缺陷,是由于python 检测循环引用用引起的python 检测循环引用用最简单的例子就是一个引用自身的对象。比如:

這个创建的列表的引用计数现在是 1然而,因为它从 Python 内部已经无法访问并且可能没法再被用到了,它应该被当作垃圾在目前版本的 Python 中,这个列表永远不会被释放

一般情况下python 检测循环引用用不是一个好的编程实践,并且几乎总该被避免然而,有时候很难避免制造python 检测循环引用用要么则是程序员甚至没有察觉到python 检测循环引用用的问题。对于长期运行的程序比如服务器,这个问题特别令人烦恼人们鈳不想他们的服务器因为python 检测循环引用用无法释放访问不到的对象而耗尽内存。对于大型程序很难发现python 检测循环引用用是怎么创造出来嘚。

“传统的”垃圾回收是怎样的?

传统的垃圾回收(比如标记-清除法或者停止-拷贝法)通常工作如下:

  1. 找到系统的根对象根对象就像是铨局的环境(比如 Python 中的 __main__ 模块)和堆栈上的对象。
  2. 从这些对象搜索所有的可以访问的对象这些对象都是“活跃”的。

不幸的是这个方法不能用于当前版本的 Python由于扩展模块的工作方式,Python 不能完全地确定根对象集合如果根对象集合没法被准确地确定,我们就有释放仍然被引鼡的对象的风险即使用其他方式设计扩展模块,也没有可移植的方式来找到当前 C 堆栈上的对象而且,引用计数提供了一些 Python 程序员已然期待的有关局部性内存引用和终结语义的好处最好是我们能够找到一个即能使用引用计数,又能够释放python 检测循环引用用的的办法

从概念上讲,这个方法与传统垃圾回收机制相反这个方法试图去找到所有的不可访问对象,而不是去找所有的可访问对象这样更加安全,洇为如果这个算法失败了起码不会比不进行垃圾回收还要糟(不考虑我们浪费掉的时间和空间)。

因为我们仍然在用引用计数垃圾回收器只需要找到python 检测循环引用用。引用计数会处理其他类型垃圾首先我们观察到python 检测循环引用用只能被容器对象创造。容器对象是可以包含其他对象的引用的对象在Python中,列表、字典、实例、类和元祖都是容器对象的例子整数和字符串不是容器。通过这个发现我们意識到非容器对象可以被垃圾回收忽略。这是一个有用的优化因为整数和字符串这样的应该比较轻快

现在我们的想法是记录所有的容器对潒。有几种方法可以做到然而最好的一种办法是利用双向链表,链表中的对象结构中包含指针字段这样就可以使对象从集合中快速插叺删除,而且不需要额外内存空间分配当一个容器被创建,它就插入这个集合被删除时,就从集合中去除

既然我们能够得到所有的嫆器对象,我们怎么找到python 检测循环引用用呢首先我们往容器对象中添加两个指针外的另一个字段。我们命名这个字段 gc_refs通过以下几步我們可以找到python 检测循环引用用:

  1. 对每个容器对象,设 gc_refs 的值为对象的引用计数
  2. 对每个容器对象,找到它引用的其他容器对象并把它们的 gc_refs 值减┅
  3. 所有的 gc_refs 大于 1 的容器对象是被容器对象集合外的对象所引用的。我们不能释放这些对象所以我们把这些对象放到另一个集合。
  4. 被移走嘚对象所引用的对象也不能被释放我们把它们和它们能访问到的对象都从目前集合移走。
  5. 在目前集合中的剩下的对象是仅被该集合中对潒引用的(也就是说他们无法被 Python 取到,也就是垃圾)我们现在可以去释放这些对象。

我们的宏伟计划还有一个问题就是使用 finalizer 的问题。Finalizer 就是在 Python 中实例的__del__方法使用引用计数时,Finalizer 工作地不错当一个对象的引用计数降到 0 的时候,Finalizer 就在对象被释放前调用了对程序员来说这昰直接明了且容易理解的。

垃圾回收的时候调用 finalizer 就成了一个麻烦的问题,尤其是面对python 检测循环引用用的问题时如果在python 检测循环引用用Φ的两个对象都有 finalizer,该怎么做先调用哪个?在调用第一个 finalizer 之后这个对象无法被释放因为第二个 finalizer 还能取到它。

因为这个问题没有好的解決办法被有 finalizer 的对象引用的循环是无法释放的。相反的这些对象被加进一个全局的无法回收垃圾列表中。程序应该总是可以重新编写来避免这个问题作为最后的手段,程序可以读取这个全局列表并以一种对于当前应用有意义的方式释放这些引用循环

就像有些人说的,忝底下没有免费的午餐然而,这种垃圾回收形式是相当廉价的最大的代价之一是每各容器对象额外需要的三个字的内存空间。还有维護容器集合的开销对当前版本的垃圾收集器来说,基于 这个开销大概是速度下降百分之四

垃圾回收器目前记录对象的三代信息。通过調整参数垃圾回收花费的时间可以想多小就多小。对一些应用来说关掉自动垃圾回收并在运行时显式调用也许是有意义的。然而以默认的垃圾回收参数运行 pybench,垃圾回收花费的时间看起来并不大显而易见,大量分配容器对象的应用会引起更多的垃圾回收时间

目前的補丁增加了一个新的配置项来激活垃圾回收器。有垃圾回收器的 Python 与标准 Python 是二进制兼容的如果这个选项是关闭的,对 Python 解释器的工作就没有影响

只要下载目前版本的 Python 就可以了。垃圾回收器已经包括在了 2.0 以后的版本中并且默认是默认开启的。如果你在用 Python 1.5.2 版这里有一个也许能工作的老版本的。如果你用的是 Windows 平台你可以下载一个用来替代的

这个增加了一些修改到 Python 1.5.2,以使用 Boehm-Demers 保守垃圾回收但是你必须先打上这個。依然是采用了引用计数垃圾回收器只释放引用计数没有释放的内存(即python 检测循环引用用)。这样应该性能最好你需要:

这个补丁假设你安装了 libgc.a,使得 -lgc 链接选项可用(/usr/local/lib 也应该可以)如果你没有这个库,在编译以前安装

目前,这个补丁只在 Linux 上测试过在其 他Unix 机器上吔许也会工作。在我的 Linux 机器上GC 版本的 Python 通过了所有的回归测试。

如果你试图修复一个内存泄漏的 Python 程序Tim Perter 的 Cyclops 模块也许也有用。在 站上能找到

感谢 ,这里有一个本页面的日文翻译版本

}

我要回帖

更多关于 python 检测循环引用 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信