溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Python C擴(kuò)展的引用計(jì)數(shù)問(wèn)題分析

發(fā)布時(shí)間:2021-11-26 09:57:05 來(lái)源:億速云 閱讀:238 作者:iii 欄目:大數(shù)據(jù)

這篇文章主要講解了“Python C擴(kuò)展的引用計(jì)數(shù)問(wèn)題分析”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Python C擴(kuò)展的引用計(jì)數(shù)問(wèn)題分析”吧!

Python GC機(jī)制

對(duì)于Python這種高級(jí)語(yǔ)言來(lái)說(shuō),開(kāi)發(fā)者不需要自己管理和維護(hù)內(nèi)存。Python采用了引用計(jì)數(shù)機(jī)制為主,標(biāo)記-清除和分代收集兩種機(jī)制為輔的垃圾回收機(jī)制。

首先,需要搞清楚變量和對(duì)象的關(guān)系:

  • 變量:通過(guò)變量指針引用對(duì)象。變量指針指向具體對(duì)象的內(nèi)存空間,取對(duì)象的值。

  • 對(duì)象,類(lèi)型已知,每個(gè)對(duì)象都包含一個(gè)頭部信息(頭部信息:類(lèi)型標(biāo)識(shí)符和引用計(jì)數(shù)器)
    Python C擴(kuò)展的引用計(jì)數(shù)問(wèn)題分析

引用計(jì)數(shù)

python里每一個(gè)東西都是對(duì)象,它們的核心就是一個(gè)結(jié)構(gòu)體:PyObject,其中ob_refcnt就是引用計(jì)數(shù)。當(dāng)一個(gè)對(duì)象有新的引用時(shí),ob_refcnt就會(huì)增加,當(dāng)引用它的對(duì)象被刪除,ob_refcnt就會(huì)減少。當(dāng)引用計(jì)數(shù)為0時(shí),該對(duì)象生命就結(jié)束了。

typedef struct_object {     int ob_refcnt;
     struct_typeobject *ob_type;
} PyObject;#define Py_INCREF(op)   ((op)->ob_refcnt++) //增加計(jì)數(shù)#define Py_DECREF(op) \ //減少計(jì)數(shù)if (--(op)->ob_refcnt != 0) \
        ; \else \
        __Py_Dealloc((PyObject *)(op))

可以使用sys.getrefcount()函數(shù)獲取對(duì)象的引用計(jì)數(shù),需要注意的是,使用時(shí)會(huì)比預(yù)期的引用次數(shù)多1,原因是調(diào)用時(shí)會(huì)針對(duì)于查詢的對(duì)象自動(dòng)產(chǎn)生一個(gè)臨時(shí)引用。

下面簡(jiǎn)單展現(xiàn)一下引用計(jì)數(shù)的變化過(guò)程。

  • 一開(kāi)始創(chuàng)建3個(gè)對(duì)象,引用計(jì)數(shù)分別是1。

  • 之后將n1指向了新的對(duì)象"JKL",則之前的對(duì)象“ABC”的引用計(jì)數(shù)就變成0了。這時(shí)候,Python的垃圾回收器開(kāi)始工作,將“ABC”釋放。

  • 接著,讓n2引用n1?!癉EF”不再被引用,“JKL”因?yàn)楸籲1、n2同時(shí)引用,所以引用計(jì)數(shù)變成了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

優(yōu)缺點(diǎn):

優(yōu)點(diǎn):實(shí)時(shí)性好。一旦沒(méi)有引用,內(nèi)存就直接釋放了。實(shí)時(shí)性還帶來(lái)一個(gè)好處:處理回收內(nèi)存的時(shí)間分?jǐn)偟搅似綍r(shí)。

缺點(diǎn):維護(hù)引用計(jì)數(shù)消耗資源;循環(huán)引用無(wú)法解決。

如下圖,典型的循環(huán)引用場(chǎng)景。對(duì)象除了被變量引用n1、n2外,還被對(duì)方的prev或next指針引用,造成了引用計(jì)數(shù)為2。之后n1、n2設(shè)成null之后,引用計(jì)數(shù)仍然為1,導(dǎo)致對(duì)象無(wú)法被回收。

Python C擴(kuò)展的引用計(jì)數(shù)問(wèn)題分析

標(biāo)記-清除、分代收集

Python采用標(biāo)記-清除策略來(lái)解決循環(huán)引用的問(wèn)題。但是該機(jī)制會(huì)導(dǎo)致應(yīng)用程序卡住,為了減少程序暫停的時(shí)間,又通過(guò)“分代回收”(Generational Collection)以空間換時(shí)間的方法提高垃圾回收效率。詳見(jiàn)Python垃圾回收機(jī)制!非常實(shí)用

Python C擴(kuò)展的引用計(jì)數(shù)

Python提供了GC機(jī)制,保證對(duì)象不被使用的時(shí)候會(huì)被釋放掉,開(kāi)發(fā)者不需要過(guò)多關(guān)心內(nèi)存管理的問(wèn)題。但是當(dāng)使用C擴(kuò)展的時(shí)候,就不這么簡(jiǎn)單了,必須需要理解CPython的引用計(jì)數(shù)。

當(dāng)使用C擴(kuò)展使用Python時(shí),引用計(jì)數(shù)會(huì)隨著PyObjects的創(chuàng)建自動(dòng)加1,但是當(dāng)釋放該P(yáng)yObjects的時(shí)候,我們需要顯示的將PyObjects的引用計(jì)數(shù)減1,否則會(huì)出現(xiàn)內(nèi)存泄漏。

#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. */}

有亮點(diǎn)尤其需要注意:

  • PyObjects引用計(jì)數(shù)為0后,不能再訪問(wèn)。類(lèi)似于C語(yǔ)言free后,不能再訪問(wèn)對(duì)象。

  • Py_INCREF、Py_DECREF必須成對(duì)出現(xiàn)。類(lèi)似于C語(yǔ)言malloc、free的關(guān)系。

Python有三種引用形式,分別為 “New”, “Stolen” 和“Borrowed” 引用。

New引用

通過(guò)Python C Api創(chuàng)建出的PyObject,調(diào)用者對(duì)該P(yáng)yObject具有完全的所有權(quán)。一般Python文檔這樣體現(xiàn):

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

針對(duì)于New引用的PyObject,有如下兩種選擇。否則,就會(huì)出現(xiàn)內(nèi)存泄漏。

  • 使用完成后,調(diào)用Py_DECREF將其釋放掉。

void MyCode(arguments) {
    PyObject *pyo;
    ...
    pyo = Py_Something(args);
    ...
    Py_DECREF(pyo);
}
  • 將引用通過(guò)函數(shù)返回值等形式傳遞給上層調(diào)用函數(shù),但是接收者必須負(fù)責(zé)最終的Py_DECREF調(diào)用。

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. */}// 錯(cuò)誤的例子,a、b兩個(gè)PyObject泄漏。r = PyNumber_Subtract(PyLong_FromLong(a), PyLong_FromLong(b));

Stolen引用

當(dāng)創(chuàng)建的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">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內(nèi)部會(huì)引用計(jì)數(shù)加一。

Borrowed引用

Python文檔中,Borrowed引用的體現(xiàn):

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

Borrowed 引用的所有者不應(yīng)該調(diào)用 Py_DECREF(),使用Borrowed 引用在函數(shù)退出時(shí)不會(huì)出現(xiàn)內(nèi)存泄露。。但是不要讓一個(gè)對(duì)象處理未保護(hù)的狀態(tài)Borrowed 引用,如果對(duì)象處理未保護(hù)狀態(tài),它隨時(shí)可能會(huì)被銷(xiāo)毀。

例如:從一個(gè) list 獲取對(duì)象,繼續(xù)操作它,但并不遞增它的引用。PyList_GetItem 會(huì)返回一個(gè) borrowed reference ,所以 item 處于未保護(hù)狀態(tài)。一些其他的操作可能會(huì)從 list 中將這個(gè)對(duì)象刪除(遞減它的引用計(jì)數(shù),或者釋放它),導(dǎo)致 item 成為一個(gè)懸垂指針。

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擴(kuò)展的引用計(jì)數(shù)問(wèn)題分析”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Python C擴(kuò)展的引用計(jì)數(shù)問(wèn)題分析這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI