Python垃圾回收

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链表中摘除,并且都能释放。解除了环引用。

热门相关:落地一把98K   让我随心所欲投资的姐姐   下女   今天妈妈,明天姐姐   宠物小精灵之庭树