亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python C擴展的引用計數問題分析

發布時間:2021-11-26 09:57:05 來源:億速云 閱讀:238 作者:iii 欄目:大數據

這篇文章主要講解了“Python C擴展的引用計數問題分析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Python C擴展的引用計數問題分析”吧!

Python GC機制

對于Python這種高級語言來說,開發者不需要自己管理和維護內存。Python采用了引用計數機制為主,標記-清除和分代收集兩種機制為輔的垃圾回收機制。

首先,需要搞清楚變量和對象的關系:

  • 變量:通過變量指針引用對象。變量指針指向具體對象的內存空間,取對象的值。

  • 對象,類型已知,每個對象都包含一個頭部信息(頭部信息:類型標識符和引用計數器)
    Python C擴展的引用計數問題分析

引用計數

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 C擴展的引用計數問題分析

標記-清除、分代收集

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">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擴展的引用計數問題分析”的內容了,經過本文的學習后,相信大家對Python C擴展的引用計數問題分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

汕尾市| 濮阳县| 阿尔山市| 石棉县| 建湖县| 靖江市| 泽州县| 马边| 鲁山县| 吉林省| 吉木乃县| 东台市| 西宁市| 建始县| 彭水| 高唐县| 美姑县| 金平| 福州市| 来凤县| 平昌县| 九龙县| 思南县| 清水县| 体育| 轮台县| 渭南市| 方山县| 沁源县| 通辽市| 印江| 城口县| 依兰县| 甘孜| 南宁市| 济南市| 唐山市| 西畴县| 瓦房店市| 清新县| 健康|