Python垃圾回收

来源:https://www.cnblogs.com/nisonge/archive/2023/09/07/17685664.html
-Advertisement-
Play Games

Vue 3 的Composition API + ``` ``` 這就把清單功能獨立出來,可在任意需要的地方復用。 基於組件去搭建應用,可實現對業務邏輯的復用。如有其他頁面也需要用到這功能,直接復用。 然後,就可基於新語法實現清單應用。 把之前的代碼移植過來後,使用ref包裹的響應式數據。修改tit ...


Python版本

v3.9.17

分析代碼的過程比較枯燥,可以直接跳轉到總結。

只能被其他對象引用類型

比如:longobject、floatobject

floatobject

以floatobject為例子來分析,先看看結構定義

typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

// 展開PyObject_HEAD後
typedef struct {
    PyObject ob_base;
    double ob_fval;
} PyFloatObject;

typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
} PyObject;

在PyObject中的_PyObject_HEAD_EXTRA,只有在編譯時指定--with-trace-refs才有效,這裡忽略即可。

./configure --with-trace-refs

可以看到在PyObject里有一個ob_refcnt的屬性,這個就是引用計數。
當對引用計數減為0時,就會調用各類型對應的析構函數。

define Py_DECREF(op) _Py_DECREF(_PyObject_CAST(op))

void _Py_Dealloc(PyObject *op)
{
    destructor dealloc = Py_TYPE(op)->tp_dealloc;
    (*dealloc)(op);
}

static inline void _Py_DECREF(PyObject *op)
{
    if (--op->ob_refcnt != 0) {
    }
    else {
        _Py_Dealloc(op);
    }
}

能引用其他對象的類型

比如listobject,dictobject...

listobject

以listobject為例子來分析,先看看結構定義

typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;

// 展開 PyObject_VAR_HEAD
typedef struct {
    PyVarObject ob_base;
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;

typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

可以看出,PyObject_VAR_HEAD也就比PyObject_HEAD多了一個Py_ssize_t ob_size而已,這個屬性是用來表示這個可變對象里元素數量。

因為可以引用其他對象,就有可能會出現環引用問題,這種問題如果再使用引用計數來作為GC就會出現問題。

lst1 = []
lst2 = []
lst1.append(lst2)
lst2.append(lst1)

當然這種情況可以使用弱引用,或者手動解除環引用。這些解決方案這裡不深入,現在主要看看python是怎樣應對這種情況。

對於這類型的對象在申請記憶體的時候調用的是PyObject_GC_New,而不可變類型是用PyObject_MALLOC。為了減少篇幅,刪掉了一些判斷邏輯。

typedef struct {
    // Pointer to next object in the list.
    // 0 means the object is not tracked
    uintptr_t _gc_next;

    // Pointer to previous object in the list.
    // Lowest two bits are used for flags documented later.
    uintptr_t _gc_prev;
} PyGC_Head;

#define FROM_GC(g) ((PyObject *)(((PyGC_Head *)g)+1))

static PyObject * _PyObject_GC_Alloc(int use_calloc, size_t basicsize)
{
    PyThreadState *tstate = _PyThreadState_GET();
    GCState *gcstate = &tstate->interp->gc;

    size_t size = sizeof(PyGC_Head) + basicsize;

    PyGC_Head *g;
    g = (PyGC_Head *)PyObject_Malloc(size);

    g->_gc_next = 0;
    g->_gc_prev = 0;
    gcstate->generations[0].count++; /* number of allocated GC objects */
    if (/* 判斷是否可以執行GC */)
    {
        gcstate->collecting = 1;
        collect_generations(tstate);
        gcstate->collecting = 0;
    }
    PyObject *op = FROM_GC(g);
    return op;
}

在可變對象中,python又加上了一個PyGC_Head。通過這個PyGC_Head將listobject鏈接到gc列表中。

在分配完listobject記憶體後,緊接著調用_PyObject_GC_TRACK,鏈接到gc列表中。

static inline void _PyObject_GC_TRACK_impl(const char *filename, int lineno,
                                           PyObject *op)
{
    PyGC_Head *gc = _Py_AS_GC(op);

    PyThreadState *tstate = _PyThreadState_GET();
    PyGC_Head *generation0 = tstate->interp->gc.generation0;
    PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
    _PyGCHead_SET_NEXT(last, gc);
    _PyGCHead_SET_PREV(gc, last);
    _PyGCHead_SET_NEXT(gc, generation0);
    generation0->_gc_prev = (uintptr_t)gc;
}

通過這裡的變數名,可以猜測使用到了分代垃圾回收。

分代回收

python手動執行垃圾回收一般調用gc.collect(generation=2)函數。

#define NUM_GENERATIONS 3

#define GC_COLLECT_METHODDEF    \
    {"collect", (PyCFunction)(void(*)(void))gc_collect, METH_FASTCALL|METH_KEYWORDS, gc_collect__doc__},

static PyObject *
gc_collect(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
    PyObject *return_value = NULL;
    int generation = NUM_GENERATIONS - 1;
    Py_ssize_t _return_value;

    _return_value = gc_collect_impl(module, generation);
    if ((_return_value == -1) && PyErr_Occurred()) {
        goto exit;
    }
    return_value = PyLong_FromSsize_t(_return_value);

exit:
    return return_value;
}

具體執行在gc_collect_impl函數中,接著往下

static Py_ssize_t gc_collect_impl(PyObject *module, int generation)
{
    PyThreadState *tstate = _PyThreadState_GET();

    GCState *gcstate = &tstate->interp->gc;
    Py_ssize_t n;
    if (gcstate->collecting) {
        /* already collecting, don't do anything */
        n = 0;
    }
    else {
        gcstate->collecting = 1;
        n = collect_with_callback(tstate, generation);
        gcstate->collecting = 0;
    }
    return n;
}

可以看到,如果已經在執行GC,則直接返回。接著看collect_with_callback

static Py_ssize_t
collect_with_callback(PyThreadState *tstate, int generation)
{
    assert(!_PyErr_Occurred(tstate));
    Py_ssize_t result, collected, uncollectable;
    invoke_gc_callback(tstate, "start", generation, 0, 0);
    result = collect(tstate, generation, &collected, &uncollectable, 0);
    invoke_gc_callback(tstate, "stop", generation, collected, uncollectable);
    assert(!_PyErr_Occurred(tstate));
    return result;
}

其中invoke_gc_callback是調用通過gc.callbacks註冊的回調函數,這裡我們忽略,重點分析collect函數。

collect函數簽名
這段代碼很長,我們拆分開來分析,這裡會去除掉一些DEBUG相關的邏輯。

static Py_ssize_t collect(PyThreadState *tstate, int generation,Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable, int nofail);
  1. 將新生代的對象合併到指定代的對象列表中。
/* merge younger generations with one we are currently collecting */
for (i = 0; i < generation; i++) {
    gc_list_merge(GEN_HEAD(gcstate, i), GEN_HEAD(gcstate, generation));
}

比如調用gc.collect(2),就表示啟動全部的垃圾回收。這裡就會將第0、1代的對象合併到第2代上。合併之後第0、1代上就空了,全部可GC的對象都在第2代上。

  1. 推斷不可達對象
/* handy references */
young = GEN_HEAD(gcstate, generation);
if (generation < NUM_GENERATIONS-1)
    old = GEN_HEAD(gcstate, generation+1);
else
    old = young;
validate_list(old, collecting_clear_unreachable_clear);

deduce_unreachable(young, &unreachable);

這裡的young指針指向第2代的鏈表頭,validate_list做校驗,這裡忽略,重點在deduce_unreachable函數中。

static inline void
deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
    validate_list(base, collecting_clear_unreachable_clear);
    update_refs(base);  // gc_prev is used for gc_refs
    subtract_refs(base);
    gc_list_init(unreachable);
    move_unreachable(base, unreachable);  // gc_prev is pointer again
    validate_list(base, collecting_clear_unreachable_clear);
    validate_list(unreachable, collecting_set_unreachable_set);
}

首先調用update_refs更新引用計數

static inline void
gc_reset_refs(PyGC_Head *g, Py_ssize_t refs)
{
    g->_gc_prev = (g->_gc_prev & _PyGC_PREV_MASK_FINALIZED)
        | PREV_MASK_COLLECTING
        | ((uintptr_t)(refs) << _PyGC_PREV_SHIFT);
}

static void
update_refs(PyGC_Head *containers)
{
    PyGC_Head *gc = GC_NEXT(containers);
    for (; gc != containers; gc = GC_NEXT(gc)) {
        gc_reset_refs(gc, Py_REFCNT(FROM_GC(gc)));
        _PyObject_ASSERT(FROM_GC(gc), gc_get_refs(gc) != 0);
    }
}

這裡的邏輯就是遍歷所有對象,然後賦值_gc_prev,設置為收集中的標識PREV_MASK_COLLECTING,然後將引用計數賦值給_gc_prev 。最後_gc_prev的內容如下。

更新完_gc_prev後,就開始調用subtrace_refs,遍歷對象中的元素,判斷元素是否也是可GC對象並且有收集中標記,如果是則減去該對象的計數。註意這裡減去的是_gc_prev中的計數,而不是真正的計數ob_refcnt

static int
visit_decref(PyObject *op, void *parent)
{
    _PyObject_ASSERT(_PyObject_CAST(parent), !_PyObject_IsFreed(op));

    if (_PyObject_IS_GC(op)) {
        PyGC_Head *gc = AS_GC(op);
        /* We're only interested in gc_refs for objects in the
         * generation being collected, which can be recognized
         * because only they have positive gc_refs.
         */
        if (gc_is_collecting(gc)) {
            gc_decref(gc);
        }
    }
    return 0;
}

static void
subtract_refs(PyGC_Head *containers)
{
    traverseproc traverse;
    PyGC_Head *gc = GC_NEXT(containers);
    for (; gc != containers; gc = GC_NEXT(gc)) {
        PyObject *op = FROM_GC(gc);
        traverse = Py_TYPE(op)->tp_traverse;
        (void) traverse(FROM_GC(gc),
                       (visitproc)visit_decref,
                       op);
    }
}

更新計數值之後,就開始收集不可達對象,將對象移入到不可達列表中。unreachable

/* A traversal callback for move_unreachable. */
static int
visit_reachable(PyObject *op, PyGC_Head *reachable)
{
    if (!_PyObject_IS_GC(op)) {
        return 0;
    }

    PyGC_Head *gc = AS_GC(op);
    const Py_ssize_t gc_refs = gc_get_refs(gc);

    if (! gc_is_collecting(gc)) {
        return 0;
    }
    assert(gc->_gc_next != 0);

    if (gc->_gc_next & NEXT_MASK_UNREACHABLE) {
        PyGC_Head *prev = GC_PREV(gc);
        PyGC_Head *next = (PyGC_Head*)(gc->_gc_next & ~NEXT_MASK_UNREACHABLE);
        _PyObject_ASSERT(FROM_GC(prev),
                         prev->_gc_next & NEXT_MASK_UNREACHABLE);
        _PyObject_ASSERT(FROM_GC(next),
                         next->_gc_next & NEXT_MASK_UNREACHABLE);
        prev->_gc_next = gc->_gc_next;  // copy NEXT_MASK_UNREACHABLE
        _PyGCHead_SET_PREV(next, prev);

        gc_list_append(gc, reachable);
        gc_set_refs(gc, 1);
    }
    else if (gc_refs == 0) {
        gc_set_refs(gc, 1);
    }
    else {
        _PyObject_ASSERT_WITH_MSG(op, gc_refs > 0, "refcount is too small");
    }
    return 0;
}

static void
move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
{
    PyGC_Head *prev = young;
    PyGC_Head *gc = GC_NEXT(young);

    while (gc != young) {
        if (gc_get_refs(gc)) {
            PyObject *op = FROM_GC(gc);
            traverseproc traverse = Py_TYPE(op)->tp_traverse;
            _PyObject_ASSERT_WITH_MSG(op, gc_get_refs(gc) > 0,
                                      "refcount is too small");
            (void) traverse(op,
                    (visitproc)visit_reachable,
                    (void *)young);
            _PyGCHead_SET_PREV(gc, prev);
            gc_clear_collecting(gc);
            prev = gc;
        }
        else {
            prev->_gc_next = gc->_gc_next;
            PyGC_Head *last = GC_PREV(unreachable);
            last->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)gc);
            _PyGCHead_SET_PREV(gc, last);
            gc->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)unreachable);
            unreachable->_gc_prev = (uintptr_t)gc;
        }
        gc = (PyGC_Head*)prev->_gc_next;
    }
    // young->_gc_prev must be last element remained in the list.
    young->_gc_prev = (uintptr_t)prev;
    // don't let the pollution of the list head's next pointer leak
    unreachable->_gc_next &= ~NEXT_MASK_UNREACHABLE;
}

這段代碼的邏輯是,遍歷收集代中的所有對象,判斷對象的計數值是否為0
如果等於0,則從收集代中移除,加入不可達列表中,然後打上不可達標記。
如果不等於0,則遍歷對象的所有元素,如果元素已經被打上不可達標記,則把該元素從不可達列表中移除,重新加入收集代列表中,並且將計數值設置為1。這是因為父對象可以被訪問,那麼子對象一定可以被訪問。

  1. 把定義了__del__的對象從不可達對象中移除
static int
has_legacy_finalizer(PyObject *op)
{
    return Py_TYPE(op)->tp_del != NULL;
}

static void
move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers)
{
    PyGC_Head *gc, *next;
    assert((unreachable->_gc_next & NEXT_MASK_UNREACHABLE) == 0);

    for (gc = GC_NEXT(unreachable); gc != unreachable; gc = next) {
        PyObject *op = FROM_GC(gc);

        _PyObject_ASSERT(op, gc->_gc_next & NEXT_MASK_UNREACHABLE);
        gc->_gc_next &= ~NEXT_MASK_UNREACHABLE;
        next = (PyGC_Head*)gc->_gc_next;

        if (has_legacy_finalizer(op)) {
            gc_clear_collecting(gc);
            gc_list_move(gc, finalizers);
        }
    }
}

這裡的邏輯就比較簡單,判斷是否定義了__del__函數,如果有,則從不可達列表中刪除,加入finalizers列表,並且清除收集中標記。

/* A traversal callback for move_legacy_finalizer_reachable. */
static int
visit_move(PyObject *op, PyGC_Head *tolist)
{
    if (_PyObject_IS_GC(op)) {
        PyGC_Head *gc = AS_GC(op);
        if (gc_is_collecting(gc)) {
            gc_list_move(gc, tolist);
            gc_clear_collecting(gc);
        }
    }
    return 0;
}

/* Move objects that are reachable from finalizers, from the unreachable set
 * into finalizers set.
 */
static void
move_legacy_finalizer_reachable(PyGC_Head *finalizers)
{
    traverseproc traverse;
    PyGC_Head *gc = GC_NEXT(finalizers);
    for (; gc != finalizers; gc = GC_NEXT(gc)) {
        /* Note that the finalizers list may grow during this. */
        traverse = Py_TYPE(FROM_GC(gc))->tp_traverse;
        (void) traverse(FROM_GC(gc),
                        (visitproc)visit_move,
                        (void *)finalizers);
    }
}

然後再遍歷finalizers列表中的所有對象,判斷對象的每個元素是否也是可GC對象,並且也有收集中標記,如果滿足條件,則從不可達列表中刪除,加入finalizers列表,並且清除收集中標記。

  1. 遍歷不可達對象列表,處理弱引用。
  2. 遍歷不可達對象列表,為每個對象調用tp_finalize函數,如果沒有則跳過。
static void
finalize_garbage(PyThreadState *tstate, PyGC_Head *collectable)
{
    destructor finalize;
    PyGC_Head seen;

    gc_list_init(&seen);

    while (!gc_list_is_empty(collectable)) {
        PyGC_Head *gc = GC_NEXT(collectable);
        PyObject *op = FROM_GC(gc);
        gc_list_move(gc, &seen);
        if (!_PyGCHead_FINALIZED(gc) &&
                (finalize = Py_TYPE(op)->tp_finalize) != NULL) {
            _PyGCHead_SET_FINALIZED(gc);
            Py_INCREF(op);
            finalize(op);
            assert(!_PyErr_Occurred(tstate));
            Py_DECREF(op);
        }
    }
    gc_list_merge(&seen, collectable);
}
  1. 處理複活的對象
static inline void
handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable,
                           PyGC_Head *old_generation)
{
    // Remove the PREV_MASK_COLLECTING from unreachable
    // to prepare it for a new call to 'deduce_unreachable'
    gc_list_clear_collecting(unreachable);

    // After the call to deduce_unreachable, the 'still_unreachable' set will
    // have the PREV_MARK_COLLECTING set, but the objects are going to be
    // removed so we can skip the expense of clearing the flag.
    PyGC_Head* resurrected = unreachable;
    deduce_unreachable(resurrected, still_unreachable);
    clear_unreachable_mask(still_unreachable);

    // Move the resurrected objects to the old generation for future collection.
    gc_list_merge(resurrected, old_generation);
}

這裡主要是上一步會調用tp_finalize函數,有可能會把一些對象複活,所以需要重新收集一次不可達對象,然後將複活的對象移入老年代中。

  1. 刪除不可達對象
static void
delete_garbage(PyThreadState *tstate, GCState *gcstate,
               PyGC_Head *collectable, PyGC_Head *old)
{
    assert(!_PyErr_Occurred(tstate));

    while (!gc_list_is_empty(collectable)) {
        PyGC_Head *gc = GC_NEXT(collectable);
        PyObject *op = FROM_GC(gc);

        _PyObject_ASSERT_WITH_MSG(op, Py_REFCNT(op) > 0,
                                  "refcount is too small");

        if (gcstate->debug & DEBUG_SAVEALL) {
            assert(gcstate->garbage != NULL);
            if (PyList_Append(gcstate->garbage, op) < 0) {
                _PyErr_Clear(tstate);
            }
        }
        else {
            inquiry clear;
            if ((clear = Py_TYPE(op)->tp_clear) != NULL) {
                Py_INCREF(op);
                (void) clear(op);
                if (_PyErr_Occurred(tstate)) {
                    _PyErr_WriteUnraisableMsg("in tp_clear of",
                                              (PyObject*)Py_TYPE(op));
                }
                Py_DECREF(op);
            }
        }
        if (GC_NEXT(collectable) == gc) {
            /* object is still alive, move it, it may die later */
            gc_clear_collecting(gc);
            gc_list_move(gc, old);
        }
    }
}

其中的邏輯也簡單,遍歷最終不可達列表,然後調用每個對象的tp_clear函數。調用後,如果對象可以被釋放,則也會從GC列表中移除。所以在後面有一個判斷if (GC_NEXT(collectable) == gc),也就是該對象還沒有被移除,這種情況則清除該對象的收集中標記,然後移入老年代中。

  1. finalizers列表中的對象移入老年代中
static void
handle_legacy_finalizers(PyThreadState *tstate,
                         GCState *gcstate,
                         PyGC_Head *finalizers, PyGC_Head *old)
{
    assert(!_PyErr_Occurred(tstate));
    assert(gcstate->garbage != NULL);

    PyGC_Head *gc = GC_NEXT(finalizers);
    for (; gc != finalizers; gc = GC_NEXT(gc)) {
        PyObject *op = FROM_GC(gc);

        if ((gcstate->debug & DEBUG_SAVEALL) || has_legacy_finalizer(op)) {
            if (PyList_Append(gcstate->garbage, op) < 0) {
                _PyErr_Clear(tstate);
                break;
            }
        }
    }

    gc_list_merge(finalizers, old);
}

所以說,定義了__del__的對象,有可能出現無法回收的情況。需要仔細編碼。

總結

python的垃圾回收主要用到了

  1. 引用計數
  2. 標記清除
  3. 分代回收

其中分代回收步驟為

  1. 將年輕代的對象移動到指定回收代的列表後。
  2. 遍歷回收代列表,將對象設置為收集中PREV_MASK_COLLECTING標記,然後將引用計數複製一份到_gc_prev中
  3. 然後遍歷每個對象中的每個元素,如果這個元素也是可GC對象,並且也有收集中標記,則將_gc_prev中的計數值減1
  4. 再遍歷回收代列表,判斷_gc_prev計數值是否為0,
    1. 如果為0,則標記為不可達,然後移動到不可達列表中。
    2. 如果不為0,則遍歷該對象的元素,如果該元素已經標記為清除,就把該元素移動到原回收代列表中。(也就是父對象仍然可達,則子對象也可達)。然後清除該對象的收集中標記。
  5. 遍歷不可達列表,清除不可達標記,判斷是否定義了__del__函數,如果有,則將清除收集中標記,並移入finalizers列表中。
  6. 遍歷finalizers列表的每個對象,判斷對象中的元素是否是可GC對象,並且有收集中標記,將該元素清除收集中標記,移入finalizers列表中。
  7. 遍歷不可達列表, 處理弱引用
  8. 遍歷不可達列表的每個對象,調用對象的tp_finalize函數,如果沒有則跳過。
  9. 遍歷不可達列表,將複活對象移到老年代列表中,其他對象移動到仍然不可達列表final_unreachable
  10. 最後遍歷 final_unreachable 列表,為每個對象調用tp_clear函數
    1. 如果真的可以刪除,則把自己從對應GC列表中摘除
    2. 如果還不能刪除,則清除對象的收集中標記,對象重新加入老年代中。
  11. finalizers列表中的每個對象重新加入老年代列表中。

例子

說到這裡好像還沒有具體分析環引用的情況

import sys
import gc


def a():
    lst1 = []
    lst2 = []

    lst1.append(lst2)
    lst2.append(lst1)

    print("lst1 refcnt: {}".format(sys.getrefcount(lst1)))
    print("lst2 refcnt: {}".format(sys.getrefcount(lst2)))

before_collect_cnt = gc.collect(2)
a()
after_collect_cnt = gc.collect(2)

print("before({}), after({})".format(before_collect_cnt, after_collect_cnt))

在筆者的電腦上輸出

hejs@ubuntu:~$ python main.py
lst1 refcnt: 3
lst2 refcnt: 3
before(0), after(2)

可以看到,在執行a函數時,lst1和lst2的引用計數為2(因為sys.getrefcount也會引用一次,所以輸出的值是真實計數+1)。
當a函數調用結束後,由於函數內的lst1、lst2變數解除了引用,所以此時兩個列表的計數值就為1了。出現環引用,無法釋放。
這個時候就輪到標記清楚和分代回收解決了。

  1. 首先會將第0、1代的元素移到第2代上。因為gc.collect(2)
  2. 然後遍歷第2代列表,為每個對象設置收集中標記,將對象的真實計數複製到_gc_prev中。
  3. 再遍歷第2代列表,判斷對象的子元素是否也是 可GC對象、也有收集中標記,如果有則將該元素計數值減1。
    1. 此時 lst1、lst2的_gc_prev計數值都為0
  4. 然後將_gc_prev計數值為0的對象移入不可達列表中。
  5. 因為listobject沒有__del__函數,也沒有tp_finalize函數,所以直接到第10步,調用tp_clear函數。
static int _list_clear(PyListObject *a)
{
    Py_ssize_t i;
    PyObject **item = a->ob_item;
    if (item != NULL) {
        i = Py_SIZE(a);
        Py_SET_SIZE(a, 0);
        a->ob_item = NULL;
        a->allocated = 0;
        while (--i >= 0) {
            Py_XDECREF(item[i]);
        }
        PyMem_FREE(item);
    }
    /* Never fails; the return value can be ignored.
       Note that there is no guarantee that the list is actually empty
       at this point, because XDECREF may have populated it again! */
    return 0;
}

也就是會為每個元素的引用計數減1。從之前分析可知,當計數減為0時,會調用對象的tp_dealloc函數,再看看listobject的tp_dealloc實現。

static void
list_dealloc(PyListObject *op)
{
    Py_ssize_t i;
    PyObject_GC_UnTrack(op);
    Py_TRASHCAN_BEGIN(op, list_dealloc)
    if (op->ob_item != NULL) {
        i = Py_SIZE(op);
        while (--i >= 0) {
            Py_XDECREF(op->ob_item[i]);
        }
        PyMem_FREE(op->ob_item);
    }
    if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op))
        free_list[numfree++] = op;
    else
        Py_TYPE(op)->tp_free((PyObject *)op);
    Py_TRASHCAN_END
}

首先會調用PyObject_GC_UnTrack,就是將該對象從GC鏈表中摘除。然後再遍歷子元素,將子元素的計數減1。計數減為0時,又會調用對象的tp_dealloc函數。

此番調用下來,lst1和lst2的計數都會被減為0,都會從GC鏈表中摘除,並且都能釋放。解除了環引用。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文給大家介紹了什麼是"編程範式",選擇合適的編程範式可以提高代碼的可讀性、可維護性和可擴展性。 一、 什麼是編程範式? "編程範式"是一種編程思想的總稱,它是指在編寫程式時所採用的基本方法和規範。常見的編程範式有面向對象、函數式、邏輯式等。 選擇合適的編程範式可以提高代碼的可讀性、可維護性和可擴展 ...
  • ### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 關於《LeetCode買賣股票》系列 - 在LeetC ...
  • 隨著金融科技的發展,越來越多的人選擇線上銀行或移動銀行上進行日常交易。在進行這些交易之前,通常需要進行身份驗證以確保賬戶的安全性。其中,銀行卡二要素驗證是一種常見的身份驗證方式。本文將為大家介紹如何使用銀行卡二要素驗證API介面,具體實現方法如下。 一、API介面介紹 銀行卡二要素驗證API介面是一 ...
  • JDK 代理和 CGLib 有什麼區別? 動態代理是一種機制,程式通過該機制在運行時動態生成代理對象並調用代理方法。動態代理主要有兩種實現機制,一種是基於反射動態代理的JDK,另一種是基於ASM動態代理機制的CGLib實現。現在讓我們談談兩種實現之間的區別以及如何實現它們 JDK 代理和 CGLib ...
  • ### 前言 上篇文章[10分鐘從源碼級別搞懂AQS(AbstractQueuedSynchronizer)](https://juejin.cn/post/7273506068104478760)說到JUC併發包中的同步組件大多使用AQS來實現 本篇文章通過AQS自己來實現一個同步組件,並從源碼級 ...
  • 函數是一組語句,可以在程式中重覆使用。函數不會在頁面載入時自動執行。函數將通過調用函數來執行。 ### 創建函數 要創建(通常稱為聲明)一個函數,請執行以下操作: - 使用 `func` 關鍵字。 - 指定函數的名稱,後跟括弧 `()`。 - 最後,在花括弧 `{}` 內添加定義函數應執行的代碼。 ...
  • 1.雙擊圖標 2.彈出如下對話框: 3、單擊按鈕Next,彈出如下對話框: 4、單擊按鈕I Agree,彈出如下對話框: 5、單擊按鈕Next,彈出如下對話框: 6、單擊Browse按鈕,可以重新設置安裝路徑 7、路徑重新設置後,單擊確定按鈕彈出如下對話框(註意,此時路徑已更改): 註意:如果想要設 ...
  • # 集合總結 ## 一、概述 1. 作用:存儲對象的容器,代替數組的,使用更加的便捷 2. 所處的位置:java.util 3. 體繫結構 ![image](https://img2023.cnblogs.com/blog/3245131/202309/3245131-202309071934421 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...