日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

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

發(fā)布時間:2024/8/23 python 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Python C扩展的引用计数问题探讨 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

簡介:?# Python GC機制 對于Python這種高級語言來說,開發(fā)者不需要自己管理和維護內(nèi)存。Python采用了引用計數(shù)機制為主,標記-清除和分代收集兩種機制為輔的垃圾回收機制。 首先,需要搞清楚變量和對象的關(guān)系: * 變量:通過變量指針引用對象。變量指針指向具體對象的內(nèi)存空間,取對象的值。 * 對象,類型已知,每個對象都包含一個頭部信息(頭部信息:類型標識符和引用計數(shù)器)

Python GC機制

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

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

  • 變量:通過變量指針引用對象。變量指針指向具體對象的內(nèi)存空間,取對象的值。
  • 對象,類型已知,每個對象都包含一個頭部信息(頭部信息:類型標識符和引用計數(shù)器)

引用計數(shù)

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

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

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

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

  • 一開始創(chuàng)建3個對象,引用計數(shù)分別是1。
  • 之后將n1指向了新的對象"JKL",則之前的對象“ABC”的引用計數(shù)就變成0了。這時候,Python的垃圾回收器開始工作,將“ABC”釋放。
  • 接著,讓n2引用n1?!癉EF”不再被引用,“JKL”因為被n1、n2同時引用,所以引用計數(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)缺點:

優(yōu)點:實時性好。一旦沒有引用,內(nèi)存就直接釋放了。實時性還帶來一個好處:處理回收內(nèi)存的時間分攤到了平時。

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

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

標記-清除、分代收集

Python采用標記-清除策略來解決循環(huán)引用的問題。但是該機制會導致應用程序卡住,為了減少程序暫停的時間,又通過“分代回收”(Generational Collection)以空間換時間的方法提高垃圾回收效率。詳見Python垃圾回收機制!非常實用

Python C擴展的引用計數(shù)

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

當使用C擴展使用Python時,引用計數(shù)會隨著PyObjects的創(chuàng)建自動加1,但是當釋放該PyObjects的時候,我們需要顯示的將PyObjects的引用計數(shù)減1,否則會出現(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. */ }

有亮點尤其需要注意:

  • PyObjects引用計數(shù)為0后,不能再訪問。類似于C語言free后,不能再訪問對象。
  • Py_INCREF、Py_DECREF必須成對出現(xiàn)。類似于C語言malloc、free的關(guān)系。

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

New引用

通過Python C Api創(chuàng)建出的PyObject,調(diào)用者對該PyObject具有完全的所有權(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.

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

  • 使用完成后,調(diào)用Py_DECREF將其釋放掉。
void MyCode(arguments) {PyObject *pyo;...pyo = Py_Something(args);...Py_DECREF(pyo); }
  • 將引用通過函數(shù)返回值等形式傳遞給上層調(diào)用函數(shù),但是接收者必須負責最終的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. */ }// 錯誤的例子,a、b兩個PyObject泄漏。 r = PyNumber_Subtract(PyLong_FromLong(a), PyLong_FromLong(b));

Stolen引用

當創(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" 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內(nèi)部會引用計數(shù)加一。

Borrowed引用

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

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

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

例如:從一個 list 獲取對象,繼續(xù)操作它,但并不遞增它的引用。PyList_GetItem 會返回一個 borrowed reference ,所以 item 處于未保護狀態(tài)。一些其他的操作可能會從 list 中將這個對象刪除(遞減它的引用計數(shù),或者釋放它),導致 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); }

?

?

原文鏈接
本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。

總結(jié)

以上是生活随笔為你收集整理的Python C扩展的引用计数问题探讨的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。