关于javascript:Python-C扩展的引用计数问题探讨

简介: # 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);
}

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理