简介: # Python GC机制 对于Python这种高级语言来说,开发者不需要自己管理和维护内存。Python采用了引用计数机制为主,标记-清除和分代收集两种机制为辅的垃圾回收机制。 首先,需要搞清楚变量和对象的关系: * 变量:通过变量指针引用对象。变量指针指向具体对象的内存空间,取对象的值。 * 对象,类型已知,每个对象都包含一个头部信息(头部信息:类型标识符和引用计数器)

Python GC机制

对于Python这种高级语言来说,开发者不需要自己管理和维护内存。Python采用了引用计数机制为主,标记-清除和分代收集两种机制为辅的垃圾回收机制。

首先,需要搞清楚变量和对象的关系:

  • 变量:通过变量指针引用对象。变量指针指向具体对象的内存空间,取对象的值。
  • 对象,类型已知,每个对象都包含一个头部信息(头部信息:类型标识符和引用计数器)

引用计数

python里每一个东西都是对象,它们的核心就是一个结构体:PyObject,其中ob_refcnt就是引用计数。当一个对象有新的引用时,ob_refcnt就会增加,当引用它的对象被删除,ob_refcnt就会减少。当引用计数为0时,该对象生命就结束了。

typedef struct_object {int ob_refcnt;struct_typeobject *ob_type;
} PyObject;#define Py_INCREF(op)   ((op)->ob_refcnt++) //增加计数
#define Py_DECREF(op) \ //减少计数if (--(op)->ob_refcnt != 0) \; \else \__Py_Dealloc((PyObject *)(op))

可以使用sys.getrefcount()函数获取对象的引用计数,需要注意的是,使用时会比预期的引用次数多1,原因是调用时会针对于查询的对象自动产生一个临时引用。

下面简单展现一下引用计数的变化过程。

  • 一开始创建3个对象,引用计数分别是1。
  • 之后将n1指向了新的对象"JKL",则之前的对象“ABC”的引用计数就变成0了。这时候,Python的垃圾回收器开始工作,将“ABC”释放。
  • 接着,让n2引用n1。“DEF”不再被引用,“JKL”因为被n1、n2同时引用,所以引用计数变成了2。

>>> n1 = "ABC"
>>> n2 = "DEF"
>>> n3 = "GHI"
>>> sys.getrefcount(n1)
2
>>> sys.getrefcount(n2)
2
>>> sys.getrefcount(n3)
2
>>> n1 = "JKL"
>>> sys.getrefcount(n1)
2
>>> n2 = n1
>>> sys.getrefcount(n1)
3
>>> sys.getrefcount(n2)
3
>>> sys.getrefcount(n3)
2

优缺点:

优点:实时性好。一旦没有引用,内存就直接释放了。实时性还带来一个好处:处理回收内存的时间分摊到了平时。

缺点:维护引用计数消耗资源;循环引用无法解决。

如下图,典型的循环引用场景。对象除了被变量引用n1、n2外,还被对方的prev或next指针引用,造成了引用计数为2。之后n1、n2设成null之后,引用计数仍然为1,导致对象无法被回收。

标记-清除、分代收集

Python采用标记-清除策略来解决循环引用的问题。但是该机制会导致应用程序卡住,为了减少程序暂停的时间,又通过“分代回收”(Generational Collection)以空间换时间的方法提高垃圾回收效率。详见Python垃圾回收机制!非常实用

Python C扩展的引用计数

Python提供了GC机制,保证对象不被使用的时候会被释放掉,开发者不需要过多关心内存管理的问题。但是当使用C扩展的时候,就不这么简单了,必须需要理解CPython的引用计数。

当使用C扩展使用Python时,引用计数会随着PyObjects的创建自动加1,但是当释放该PyObjects的时候,我们需要显示的将PyObjects的引用计数减1,否则会出现内存泄漏。

#include "Python.h"void print_hello_world(void) {PyObject *pObj = NULL;pObj = PyBytes_FromString("Hello world\n"); /* Object creation, ref count = 1. */PyObject_Print(pLast, stdout, 0);Py_DECREF(pObj);    /* ref count becomes 0, object deallocated.* Miss this step and you have a memory leak. */
}

有亮点尤其需要注意:

  • PyObjects引用计数为0后,不能再访问。类似于C语言free后,不能再访问对象。
  • Py_INCREF、Py_DECREF必须成对出现。类似于C语言malloc、free的关系。

Python有三种引用形式,分别为 “New”, “Stolen” 和“Borrowed” 引用。

New引用

通过Python C Api创建出的PyObject,调用者对该PyObject具有完全的所有权。一般Python文档这样体现:

PyObject* PyList_New(int len)Return value: New reference.Returns a new list of length len on success, or NULL on failure.

针对于New引用的PyObject,有如下两种选择。否则,就会出现内存泄漏。

  • 使用完成后,调用Py_DECREF将其释放掉。
void MyCode(arguments) {PyObject *pyo;...pyo = Py_Something(args);...Py_DECREF(pyo);
}
  • 将引用通过函数返回值等形式传递给上层调用函数,但是接收者必须负责最终的Py_DECREF调用。
void MyCode(arguments) {PyObject *pyo;...pyo = Py_Something(args);...return pyo;
}

使用样例:

static PyObject *subtract_long(long a, long b) {PyObject *pA, *pB, *r;pA = PyLong_FromLong(a);        /* pA: New reference. */pB = PyLong_FromLong(b);        /* pB: New reference. */r = PyNumber_Subtract(pA, pB);  /*  r: New reference. */Py_DECREF(pA);                  /* My responsibility to decref. */Py_DECREF(pB);                  /* My responsibility to decref. */return r;                       /* Callers responsibility to decref. */
}// 错误的例子,a、b两个PyObject泄漏。
r = PyNumber_Subtract(PyLong_FromLong(a), PyLong_FromLong(b));

Stolen引用

当创建的PyObject传递给其他的容器,例如PyTuple_SetItem、PyList_SetItem。

static PyObject *make_tuple(void) {PyObject *r;PyObject *v;r = PyTuple_New(3);         /* New reference. */v = PyLong_FromLong(1L);    /* New reference. *//* PyTuple_SetItem "steals" the new reference v. */PyTuple_SetItem(r, 0, v);/* This is fine. */v = PyLong_FromLong(2L);PyTuple_SetItem(r, 1, v);/* More common pattern. */PyTuple_SetItem(r, 2, PyUnicode_FromString("three"));return r; /* Callers responsibility to decref. */
}

但是,需要注意PyDict_SetItem内部会引用计数加一。

Borrowed引用

Python文档中,Borrowed引用的体现:

PyObject* PyTuple_GetItem(PyObject *p, Py_ssize_t pos)
Return value: Borrowed reference.

Borrowed 引用的所有者不应该调用 Py_DECREF(),使用Borrowed 引用在函数退出时不会出现内存泄露。。但是不要让一个对象处理未保护的状态Borrowed 引用,如果对象处理未保护状态,它随时可能会被销毁。

例如:从一个 list 获取对象,继续操作它,但并不递增它的引用。PyList_GetItem 会返回一个 borrowed reference ,所以 item 处于未保护状态。一些其他的操作可能会从 list 中将这个对象删除(递减它的引用计数,或者释放它),导致 item 成为一个悬垂指针。

bug(PyObject *list) {PyObject *item = PyList_GetItem(list, 0);PyList_SetItem(list, 1, PyInt_FromLong(0L));PyObject_Print(item, stdout, 0); /* BUG! */
}no_bug(PyObject *list) {PyObject *item = PyList_GetItem(list, 0);Py_INCREF(item); /* Protect item. */PyList_SetItem(list, 1, PyInt_FromLong(0L));PyObject_Print(item, stdout, 0);Py_DECREF(item);
}

原文链接
本文为阿里云原创内容,未经允许不得转载。

Python C扩展的引用计数问题探讨相关推荐

  1. 扩展Python模块系列(四)----引用计数问题的处理

    承接上文,发现在使用Python C/C++ API扩展Python模块时,总要在各种各样的地方考虑到引用计数问题,稍不留神可能会导致扩展的模块存在内存泄漏.引用计数问题是C语言扩展Python模块最 ...

  2. Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(异常处理和引用计数)

    我们将继续一步一步动手给Python写扩展,通过上一篇我们学习了如何写扩展,本篇将介绍一些高级话题,如异常,引用计数问题等.强烈建议先看上一篇,Python之美[从菜鸟到高手]--一步一步动手给Pyt ...

  3. Python线性代数扩展库numpy.linalg中几个常用函数

    本文内容节选自董付国老师2000页Python系列课件第17章"数据分析.科学计算.可视化.机器学习"(本章PPT共410页). ----------相关阅读---------- ...

  4. 如何用C++ 写Python模块扩展(一)

    最近做一个小软件需要用到虚拟摄像头,在网上找了找虚拟摄像头软件 发现 Vcam 软件有个API 可以用,有API当然是最好的啦,但是这个API只有C++和C#的.都说 "人生苦短,得用pyt ...

  5. python深入和扩展_用Python来扩展Postgresql(一)

    本文主要是通过简单的例子演示一下如何在Postgresql里面写Python代码. 1.在Postgresql的机器上安装Python.(这貌似是废话) 2.检查Postgresql的安装目录下面的 ...

  6. 解决无法使用pip命令加载Python的扩展库问题

    解决无法使用pip命令加载Python的扩展库问题 参考文章: (1)解决无法使用pip命令加载Python的扩展库问题 (2)https://www.cnblogs.com/duanxueyuan/ ...

  7. python 变量传值传引用 区分

    传值:strings tuples numbers 传引用: list dict python的变量都可以看成是内存中某个对象的引用.(变量指向该内存地址存储的值) 1.python中的可更改对象和不 ...

  8. python导入其他py文件-Python中py文件引用另一个py文件变量的方法

    最近自己初学Python,在编程是遇到一个问题就是,怎样在一个py文件中使用另一个py文件中变量,问题如下: demo1代码 import requests r = requests.get(&quo ...

  9. Python常用扩展包

    一. Python常用扩展包 参考张良均的<Python数据分析与挖掘实战>,下图展示了常见的Python扩展包.         常用的包主要包括:         1.Numpy    ...

最新文章

  1. 适用于任何数据可视化需求的国外10个最佳JavaScript图表库
  2. NLP复习资料(3)-六~七章:马尔科夫模型与条件随机场、词法分析与词性标注
  3. android recycleview长按多选_UI设计中Android和IOS设计差异总结
  4. 常用服务器安装和部署
  5. Doris之分区缓存(全面)
  6. c++ 17 std::variant
  7. 【全网最详细】 树莓派控制ws2812b灯带 点亮教程
  8. 思维导图制作工具BLUMIND 小巧免激活 免安装 仅2M
  9. 【PTA 6-10】输入多个单词,统计以指定字母开头的单词个数
  10. 高中学计算机都是学什么,学计算机高中选什么科目
  11. 拼多多迈向后黄峥时代
  12. 系统之家 Win10_21H1 x64位 游戏专业版系统下载 2021.06
  13. 生成html水印,兼容ie8,ie兼容性问题与解决办法,移动端水印给出的建议
  14. win10+Ubuntu双系统安装/卸载/扩容/同步时间
  15. unity寻找指定预设并操作
  16. 2020-11-14
  17. js控制屏幕双屏显示
  18. python 数据分析师 考试_数据分析师证书怎么考?
  19. python 打开电脑摄像头
  20. datagridview取消默认选中_DataGridView点击空白处和失去焦点后取消选择和关闭默认选择第一行C#Winform...

热门文章

  1. 任意点 曲线距离_中级数学11-曲线函数
  2. 什么是面向对象_什么是面向对象?新手程序员必掌握的技能
  3. sqlserver如何从周一计算周_纯零基础,花10周时间,完全搞定Python,有没有可能?...
  4. lichee linux nfs,SPI Flash 系统编译
  5. mysql load data outfile_mysql load data infile和into outfile的常规用法:
  6. python利用tensorflow识别圆_RaspberryPi上实现佩戴口罩识别——2020电赛F题小记
  7. 互联网技术+非技术书单资源分享,都给泥萌!
  8. python自动输入账号密码_Python如何基于selenium实现自动登录博客园
  9. php获取指定日期的万年历,分享3个php获取日历的函数
  10. java putifabsent_java8中Map的一些骚操作总结