在处理C代码时,是否存在任何Python引用计数/垃圾收集陷阱?

6
为了好玩,我决定创建一个 Scheme绑定到libpython,这样您就可以在Scheme程序中嵌入Python。我已经能够调用Python的C API,但我还没有真正考虑过内存管理。
mzscheme的FFI工作方式是,我可以调用一个函数,如果该函数返回一个指向PyObject的指针,则可以自动增加引用计数。然后,我可以注册一个终结器,在Scheme对象被垃圾收集时减少引用计数。我已经查看了参考计数文档,乍一看似乎没有任何问题(尽管在某些情况下可能不是最优的)。我是否漏掉了什么要注意的地方?
此外,我很难理解循环垃圾收集器文档。我需要注意哪些事项?特别是,我如何让Python意识到我对某些东西有引用,以便在我仍在使用它时不会将其收集?
2个回答

8
您的链接指向http://docs.python.org/extending/extending.html#reference-counts,这是正确的位置。文档中的扩展和嵌入以及Python/C API部分将解释如何使用C API。
引用计数是使用C API时最麻烦的部分之一。主要问题在于保持一切清晰:根据您调用的API函数,您可能拥有或借用获取到的对象的引用。要注意理解您是否拥有它(因此不能忘记对其进行DECREF或将其提供给将窃取它的内容)或者是借用它(并且必须INCREF以保留它并可能在函数期间使用它)。与此相关的最常见错误是1)错误地记住您是否拥有特定函数返回的引用,以及2)认为您可以安全地借用引用的时间比实际上更长。
您无需为循环垃圾收集器做任何特殊处理。它只是用来修补引用计数中的缺陷,并不需要直接访问。

2
@Jason,只增加借用的引用计数。有些函数返回已经增加引用计数的引用,再次增加会导致内存泄漏。 - Virgil Dupras
4
@Eli,CPython中杀死对象的主要策略是引用计数。由于引用计数无法识别其他不可访问的引用循环,因此它与(默认情况下打开的)循环垃圾回收器相结合使用。这种增强是必要的,以防止在允许任意引用的任何引用计数系统中发生内存泄漏。只有在引用计数是设计缺陷的情况下才存在这个设计缺陷(当然,有些人会这样认为)。 - Mike Graham
1
@Jason,不是这样的!有时候你会借用一个引用(需要INCREF它以拥有对象的引用),有时候你会通过调用函数窃取一个引用并已经拥有它;在这种情况下,INCREF可能会导致内存泄漏。同样,何时DECREF取决于某个函数是否窃取了你的引用,这种情况也会发生。不同的函数会给出或借出它们返回的引用,并窃取或借用它们接收到的引用。记住你正在使用的函数做什么是为什么这可能成为一个问题的来源。 - Mike Graham
@Jason,http://docs.python.org/c-api/intro.html#reference-count-details讨论了我在上一条评论中提到的内容。 - Mike Graham
1
@Eli,你没有清楚地向我表达;我认为你特别声称用循环查找增强引用计数是一个设计问题。无论你喜欢引用计数与否,这都是一个更大的问题,更多的是宗教信仰而不是技术问题。 - Mike Graham
显示剩余6条评论

3
我知道与参考计数和C API相关的最大陷阱是__del__。当您借用某个东西时,您认为您可以不进行INCREF,因为在使用该引用时您不放弃GIL。但是,如果您删除一个对象(例如从列表中删除它),则可能会触发__del__调用,这可能会在您借用的引用下面删除引用。非常棘手。
如果您在获得所有借用的引用时进行INCREF(然后当然进行DECREF),就不应该有任何问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接