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

38次阅读

共计 4055 个字符,预计需要花费 11 分钟才能阅读完成。

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

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

正文完
 0