zoukankan      html  css  js  c++  java
  • 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那就必须要进行增量操作对变量进行保护了。

    像这样的情况,何时对对象的计数器增量,何时减量,完全可以有编程人员自己判断,如果不能判断则就按照规则来。

  • 相关阅读:
    Apache Solr学习 第二篇 solr安装(windows)
    Apache Solr学习 第一篇 简介和安装环境准备
    第二篇 redis常用操作命令
    第一篇 redis简介及安装
    使用maven安装jar包到本地仓库时遇到The goal you specified requires a project to execute but there is no POM in this directory错误
    使用Arrays.asList()时遇到的问题
    linux下zookeeper的安装
    第六篇 mybatis的运行原理(3):工厂方法模式,SqlSession的创建
    leetcode 剑指 Offer 35. 复杂链表的复制
    leetcode 112. 路径总和
  • 原文地址:https://www.cnblogs.com/Leon-The-Professional/p/10137392.html
Copyright © 2011-2022 走看看