Python中的引用計數法


引用計數法

增量操作

如果對象的引用數量增加,就在該對象的計數器上進行增量操作。在實際中它是由宏Py_INCREF() 執行的。

#define Py_INCREF(op) (((PyObject*)(op))->ob_refcnt++)  
#define Py_XINCREF(op) if ((op) == NULL) ; else Py_INCREF(op)

除了增量操作外,還要執行NULL檢查,Py_XINCREF(op)。

計數器溢出的問題

Include/object.h

typedef ssize_t        Py_ssize_t;

ssize_t型,在32位環境下是int在64位下是long,它的大小由系統決定。這里定義的計數器它是可以為負數的,那么就有問題了,計數器是有符號整數,他能表達的最大數僅僅是無符號整數的一半。這樣不會內存溢出嗎?我們之前說對象是4字節對齊的,既然是按4字節對齊,我們就可以得到這樣分下來,即使所有對象都指向某一個對象,也是不會溢出的。

那么,負的計數器表達的是什么?在debug中,會存在減數操作過度,和增量操作遺失的情況。負的計數器就是為它而設計的。在debug中,Py NegativeRefcount() 函數會把變為負數的對象信息當成錯誤信息輸出。

減量操作

  • 先將計數器減量
  • 如果得出0以外的數值就調用_Py_CHECK_REFCNT()。它負責檢查引用計數器是否變為負數。
  • 如果計數器為0就調用 _Py_Dealloc(),與增量操作相同,這里是減量操作。
  • NULL檢查擴展的減量操作。

其中成員 tp_dealloc 存着負責釋放各個對象的函數指針,比如下面這個釋放元組對象的函數指針。

Objects/tupleobject.c

static void tupledealloc(register PyTupleObject *op) 
{    
    register Py_ssize_t i;
    register Py_ssize_t len = Py_SIZE(op);
    if (len > 0) {
        i = len;
        /* 將元組內的元素進行減量 */
        while (--i >= 0)
            Py_XDECREF(op->ob_item[i]);
    }
    /* 釋放元組對象 */
    Py_TYPE(op)->tp_free((PyObject *)op);
    
    Py_TRASHCAN_SAFE_END(op) }

  • 先對元組進行減量,然后在去釋放對象。
  • 成員tp_free里存着各個對象的釋放處理程序。調用PyObject_GC_Del()

PyObject_GC_Del()

void PyObject_GC_Del(void *op) {
    PyGC_Head *g = AS_GC(op);
    /* 省略部分:釋放前的處理 */
    PyObject_FREE(g);
}

這里的 PyObject_FREE(),就是上一節中的 PyObject_Free()函數,這個函數會對對象進行釋放。不過我是怎么知道的呢。此處又有宏定義。#define PyObject_FREE PyObject_Free。位於Include/objimpl.h

元組減量操作如下圖示:

終結器

就是我們類里經常寫的 __del__

終結器指的是與對象的釋放處理掛鈎的一個功能。列表和字典等內置對象基本上是不能設置終結器的,能定義終結器的只有用戶創建的類

# 一個終結器
class Foo(object):
    def __def__(self):  # 定義終結器
        print("GKD") 

這種情況下,當Foo被釋放的時候,就會輸出GKD。

那么Foo實例實際上是怎么調用的呢?如下示:

Objects/typeobject.c:subtype_dealloc():單獨拿出終結器的部分

static void subtype_dealloc(PyObject *self)
{    
    PyTypeObject *type, *base;
    destructor basedealloc;
    type = Py_TYPE(self);
    if (type->tp_del) { 
    _PyObject_GC_TRACK(self);
    type->tp_del(self);
    
    }
    /* 省略 */
}

實例的情況下,變量 tp_del 中保存着執行終結器所需的 slot_tp_del() 函數

Objects/typeobject.c:slot_tp_del()

static void
slot_tp_del(PyObject *self)
{
    static PyObject *del_str = NULL;
    PyObject *del, *res;
    self->ob_refcnt = 1;
    
    /* 如果有__del__就執行它 */
    del = lookup_maybe(self, "__del__", &del_str);
    if (del != NULL) {
        res = PyEval_CallObject(del, NULL);
        
        /* 省略部分:錯誤檢查和后處理等 */
    }
      
      
    if (--self->ob_refcnt == 0)
        return; /* 退出函數 */
        
    /* 省略部分:最終化時有引用的情況下的應對處理 */
}

先用lookup_maybe(),取出實例中的__del__,然后使用 PyEval_CallObject()來執行它。

插入計數處理

在python中,正常情況是要對對象的計數器進行增量和減量操作的。但是並不是所有地方都需要這樣做。

比如說在python中編寫c的擴展模塊:當從局部變量引用某個對象,大多數情況下是可以不執行計數處理的,因為從局部來說,我們引用它之后給計數器增量,退出后局部后又要減量。這實際上沒有任何意義。不過也可以這樣做。

本來計數器的作用是告訴GC這個對象被引用了,不要回收。那如果計數器的值已經是大於0了。我們還需要這樣的增量計數器嗎?增量之后計數器局部使用完后還是會被減量的。

但是在局部變量的作用域中,如果對象的計數器為0那就必須要進行增量操作對變量進行保護了。

像這樣的情況,何時對對象的計數器增量,何時減量,完全可以有編程人員自己判斷,如果不能判斷則就按照規則來。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM