写点什么

从内存泄露聊聊 python 内存管理

  • 2024-05-16
    湖南
  • 本文字数:1140 字

    阅读完需:约 4 分钟

在使用 cpython 时, 发现偶尔会发生内存泄露。这是什么原因呢?

从 python 内存管理机制开始说起

默认的内存分配器

python 中所有内存管理机制都有两套实现,通过编译符号 PYMALLOC_DEBUG 控制,在 debug 模式下可以记录很多关于内存的信息,方便开发时进行调试。

python 内存管理机制

python 内存管理机制大致被分为四层

  1. 操作系统提供的内存管理接口,比如 malloc 和 free 接口,由操作系统实现和管理

  2. Python 对于第 0 层的一个简单包装,主要是为了统一不同操作系统的行为,以 PyMem_为前缀

  3. python 创建对象时不仅仅需要申请一块内存空间,还需要管理对象的类型参数以及初始化对象的引用计数值。以 PyObj_为前缀。

  4. 最上层针对一些常用的字符串对象和整数对象,提供一个对象缓存的机制。

而在 Python 中,对象间的引用是通过引用计数来管理的。当一个对象被引用时,其引用计数会增加;当不再被引用时,其引用计数会减少。当引用计数为 0 时,该对象就会被垃圾回收器回收。

PyTuple_SetItem 源码分析

C++复制代码intPyTuple_SetItem(PyObject *op, Py_ssize_t i, PyObject *newitem){    PyObject **p;    if (!PyTuple_Check(op) || op->ob_refcnt != 1) {        Py_XDECREF(newitem);        PyErr_BadInternalCall();        return -1;    }    if (i < 0 || i >= Py_SIZE(op)) {        Py_XDECREF(newitem);        PyErr_SetString(PyExc_IndexError,                        "tuple assignment index out of range");        return -1;    }    p = ((PyTupleObject *)op) -> ob_item + i;    Py_XSETREF(*p, newitem);    return 0;}
复制代码

首先 python 中 元组应该是不可变的,因此只有当其引用计数为 1,也就是没有其他人调用这个元组时,我们才可以对其元素进行更改。 因此最开始先要确认是否是元组类型以及元祖的引用计数是否为 1,如果不满足要求,则会让 newitem 的引用计数减少 1。

注意:如果在此之前 newitem 的引用计数为 1 ,经过减少则会变成 0,那么这个时候就相当于此函数会“偷走”一个对 newitem 的引用。可能会导致该元素提前被释放. 这可能会造成内存风险,不过不是泄露。

接下来会进行索引有效性检查,风险同上,这里不过多赘述。

接下来会进行赋值,这个过程中,旧的元素的引用计数会减少(如果变成 0,也会提前被释放,不过这里是符合预期的),新的元素的计数会增加。

总结

也就是说 PyTuple_SetItem 存在两种可能,第一种:新元素引用计数减 1,第二种旧元素引用计数减 1,新元素引用计数加 1

因此我们必须要关注 PyTuple_SetItem 的操作是否会成功,如果成功,我们需要手动释放之前创建的 new_item(更准确的说,是减引用计数)。如果失败,我们就不能对 new_item 有任何操作。


作者:山花

链接:https://juejin.cn/post/7366972839780089906

来源:稀土掘金

用户头像

欢迎关注,一起学习,一起交流,一起进步 2020-06-14 加入

公众号:做梦都在改BUG

评论

发布
暂无评论
从内存泄露聊聊python内存管理_Python_我再BUG界嘎嘎乱杀_InfoQ写作社区