Python C扩展的引用计数问题探讨

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

Python GC机制

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

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

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

引用计数

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。

image.png

>>> 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,导致对象无法被回收。

image.png

标记-清除、分代收集

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);
}

 

 

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/515170.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

云原生时代 RocketMQ 运维管控的利器 - RocketMQ Operator

作者 | 刘睿、杜恒 导读:RocketMQ Operator 现已加入 OperatorHub,正式进入 Operator 社区。本文将从实践出发,结合案例来说明,如何通过 RocketMQ Operator 在 Kubernetes 上快速搭建一个 RocketMQ 集群,并提供一些 Ro…

Spring Security BadCredentialsException: Bad credentials问题解决

问题描述: org.springframework.security.authentication.BadCredentialsException: Bad credentials 问题分析: 1、数据库里面的密码没有加密,导致输入正确密码也匹配不上。 解决办法:使用PasswordEncoder先将当前密码加密&…

携程在港挂牌:两次疫情两次上市 穿越周期初心不灭

携程香港联合交易所上市庆祝仪式现场 2021年4月19日9点30分,携程集团正式在香港联合交易所上市,股份代号:9961.hk。这是继2003年在美国纳斯达克上市后,携程在香港的第二次上市。在上海市凌空SOHO携程总部大楼前,8位携程客人代表数…

淘宝直播在智能互动领域的探索及落地

简介: 随着带宽成本的降低和端上算力的提升,在直播、短视频中基于流的互动玩法也将越来越丰富;无论是基于人脸、手势、分割算法的智能化贴纸,还是结合算法的小游戏有了越来越好的舞台;业界短视频和直播主要针对的娱乐性…

基于实时计算Flink的机器学习算法平台及场景介绍

作者:高旸(吾与),阿里巴巴高级技术专家 1. 前言 随着互联网“人口红利”的“消耗殆尽”,基于“T1”或者离线计算的机器学习平台及推荐系统转化率与效果日趋“平淡”。后疫情时代的新社会模式及经济形态必将催生出新的…

SpringCloud 应用在 Kubernetes 上的最佳实践 — 线上发布(可监控)

简介: 本篇是“SpringCloud 应用在 Kubernetes 上的最佳实践”系列文章的第六篇,主要介绍了如何保障生产环境服务稳定,做到随时发布,从而加快业务的迭代和上线速度。 文:骐羽 前言 在应用发布上线的时候我们最担心的…

mPaaS:全新移动开发平台,只为打造性能更优越的App

简介: 基于移动开发现状与技术演进预判,提供移动开发强力解决方案,洞察 mPaaS 如何帮助企业有效降低技术门槛,减少研发成本,搭建更稳定、更流畅的移动 App。 mPaaS 是源自于支付宝的移动开发平台,为移动开发…

德勤2021技术趋势:繁琐、点状的匠人AI时代将终结,MLOps时代来临

作者 | 宋慧 出品 | CSDN 头图 | 付费下载于视觉中国 德勤在近日发布《2021 年技术趋势》报告,这已经是德勤连续第十二年发布全球技术趋势报告。纵观今年的九大技术趋势,德勤给出的主题关键词是“韧性”,建议企业运用技术实现敏捷发展、构建…

Security RBAC 表结构+权限查询sql

文章目录1. 表结构2. 权限查询sql1. 表结构 /*Navicat Premium Data TransferSource Server : 127.0.0.1Source Server Type : MySQLSource Server Version : 80026Source Host : localhost:3306Source Schema : sys-adminTarget Server Type …

蚂蚁架构师郭援非:分布式数据库是金融机构数字化转型的最佳路径

简介: OceanBase立志于成为世界领先的企业级数据技术解决方案提供商。 2020年8月26-28日,在中科软科技举办的中国财险科技应用高峰论坛上,蚂蚁集团高级解决方案架构师郭援非发表了《OceanBase分布式关系数据库助力保险业务创新》的主题演讲&…

实锤!Python 真没你想的那么简单…

首先我不可否认,Python确实很“火”!很多开发者都把它当做主语言或是第二语言。当做主语言原因很好理解,因为最近几年它在人工智能、数据分析、Web开发等众多领域都有着非常成熟的应用。当做第二语言,是因为用它来完成多线程、数据…

jdbcUrl is required with driverClassName错误解决

springboot 升级到2.0之后发现配置多数据源的时候报错: “jdbcUrl is required with driverClassName.”或者Cause: java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.] with root cause 主要原因是在1.0 配置数据…

用根因定位法,让运维效率再高一点!

‍‍作者 | 中国农业银行研发中心 王哲头图 | 下载于视觉中国出品 | CSDN云计算(ID:CSDNcloud)随着业务不断发展,微服务架构越来越受到各大企业的青睐,随之也给传统运维带来更大的挑战,多维KPI指标数量繁多…

零基础开发 nginx 模块

简介: 推荐学习资料: * nginx 开发指南: http://nginx.org/en/docs/dev/development_guide.html * nginx 动态模块编译博客文章: https://www.nginx.com/blog/compiling-dynamic-modules-nginx-plus/ * nginx 源码: https://github.com/nginx 推荐学习资料: nginx…

勒索病毒如何防治?看阿里云双拳出击不留隐患

简介: 阿里云与合作伙伴Commvault联合发布勒索病毒防治解决方案,从公共云、混合云等场景入手,提供完善的解决方案 在众多的数据安全工作中,勒索病毒的防治是近几年备受关注的领域。从互联网诞生伊始,勒索病毒就相生相…

中国五大移动应用商店携手启动64位安卓生态迁移

为协助中国安卓开发者无缝对接全球64位开发环境,并进一步向终端用户提供最佳体验,中国五大移动应用商店(排名不计先后)—小米应用商店、OPPO软件商店、vivo应用商店、腾讯应用宝、百度手机助手,于今日共同宣布&#xf…

滴滴基于 Flink 的实时数仓建设实践

随着滴滴业务的高速发展,业务对于数据时效性的需求越来越高,而伴随着实时技术的不断发展和成熟,滴滴也对实时建设做了大量的尝试和实践。本文主要以顺风车这个业务为引子,从引擎侧、平台侧和业务侧各个不同方面,来阐述…

闲鱼直播三周内实现点击率翻倍,我们是这么做到的...

作者:闲鱼技术-莫癫 1. 业务背景 闲鱼直播业务上线后面临的最大问题是增长问题。闲鱼BI同学分析发现,对比短时观看和长时观看人群,发现两部分人群有较明显的兴趣阶段性差异。 业务希望在理解直播、主播和用户的基础根据兴趣对头部优质直播精…

Azure 中国四年扩容 12 倍还不够,微软放话:全球每年新建 50-100 数据中心!

数据已渗透到我们生活和工作的方方面面,如今全球正处于经济发展转型与变革的关键时期,数据作为数字经济的核心生产要素,无疑建设先进的数据中心是科技企业的硬核 IT 实力的有力保证,科技巨头纷纷强势布局数据中心: 据…

打造数字化服务能力,中国联通如何借助云原生技术实现增长突围?

简介: 中国联通与阿里云结合阿里云原生 PaaS、阿里飞天操作系统、阿里云原生数据库以及中国联通天宫平台,共同研发运营商级专有云平台“天宫云”,支撑中国联通核心业务应用。 8 月 13 日,中国联通发布《2020 年半年度报告》&#…