Python Weakref弱引用

发布时间 2023-10-04 20:43:48作者: Gensokyo_Alice

背景

在开始讨论弱引用的实现之前,我们先来看看什么是弱引用以及弱引用的作用。

在我们平常编写代码的时候我们经常会使用引用,了解一下Python,我们就会知道,当一个对象的引用为0时,才会销毁该对象。

那么比如我们现在需要维护这样一个东西:
不断向一个多线程程序发送数据,同时引入一个缓存来存储数据,但是当没有人使用该数据的时候,销毁这个缓存来节省空间。

那么一个很显然的思路是建立一个 dict,然后不断往 dict 里面添加信息,这时候我们就会注意到这样的问题:dict 存储的信息都是引用,当我们使用完了数据,还是会留下一个dict中的引用,这样数据并不会被销毁,而且会越堆积越多。

示例代码如下:

import threading


class Data:
    def __init__(self, key):
        pass


class Cacher:
    def __init__(self):
        self.pool = {}
        self.lock = threading.Lock

    def get(self, key):
        with self.lock:
            data = self.pool.get(key)
            if data:
                return data
            self.pool[key] = data = Data(key)
            return data

如何解决引用带来的内存占用

那么这个时候就要用到我们的弱引用了。

弱引用并不会增加引用数,但是它会关联目标对象。
如图所示
弱引用示例图

当我们删除引用时,就会变成如下情况
删除引用示例图

这样一来,我们只需要把缓存改成弱引用即可。

class Cacher:
    def __init__(self):
        self.pool = {}
        self.lock = threading.Lock

    def get(self, key):
        with self.lock:
            data = self.pool.get(key)
            if data:
                return data
            data = Data(key)
            self.pool[key] = weakref.ref(data)
            return data

由于缓存 dict 只保存 Data 对象的弱引用,因此 Cacher 不会影响 Data 对象的引用计数,当所有线程用完之后,引用计数就归零,数据就会被释放。

实际上用字典缓存数据对象很常见,所以weakref模块还提供了两种只保存弱引用的字典对象:

  • weakref.WeakKeyDictionary 键只保存弱引用
  • weakref.WeakValueDictionary 值只保存弱引用

所以我们也可以通过把 dict 改成 WeakValueDictionary 来实现我们想要的功能

class Cacher:
    def __init__(self):
        self.pool = weakref.WeakValueDictionary()
        self.lock = threading.Lock

    def get(self, key):
        with self.lock:
            data = self.pool.get(key)
            if data:
                return data
            self.pool[key] = data = Data(key)
            return data

工作原理

然后我们就可以去阅读源代码了,笔者这里直接找到了 CPython 实现

注意:3.11以后的代码中的单独有一个头文件来做 typedef,读者不必纠结 PyObecjt 从哪里来
笔者所看的为3.11 版本号c8de883bcb022b59d7ae6d20993599b47e4b968b

在 /Objects/weakrefobject.c 和 /Include/weakrefobject.h 中
很容易就可以发现 PyWeakReference 包含如下字段:
PyWeakReference字段

我们主要解释 wr_object, wr_callback, hash, wr_prevwr_next

  • wr_object 是对象指针,指向被引用的对象,弱引用根据该字段找到被引用的对象,但是不会产生引用。
  • wr_callback 指向一个可调用对象,当被引用的对象销毁时调用
  • hash 缓存被引用对象的哈希值
  • wr_prevwr_next 分别是前后向指针,用于将弱引用对象组织成双向链表。

所以一个对象的弱引用组织结构如下
弱引用组织结构

  • 弱引用对象通过 wr_object 字段来关联被引用对象,就像上面图中红色箭头一样
  • 一个对象可以同时被多个弱引用对象关联,图中的 Data 实例被两个弱引用对象关联 weakrefobject.c 943行
  • 当一个对象被销毁了之后,Python将遍历一遍弱引用链表,逐一处理。
    • wr_object 设置为 None 具体是现在 weakrefobject.c 中第55行
    • 执行回调函数 wr_callback(如果有)

由此可见,弱引用的工作原理就是设计模式中的 观察者模式。当对象被销毁,所有的弱引用对象都得到通知并进行处理。

一些实现细节后面更新……