工作内容中需要处理引用关系,并查阅了此篇文献,记录分享!
该内容原文出自:https:
如有侵权,联系必删!!!
开始讨论弱引用(
weakref
)之前,我们先来看看什么是弱引用?它到底有什么作用?
假设我们有一个多线程程序,并发处理应用数据:
class Data:\
def __init__(self, key):\
应用数据 Data 由一个 key 唯一标识,同一个数据可能被多个线程同时访问。由于 Data 需要占用很多系统资源,创建和消费的成本很高。我们希望 Data 在程序中只维护一个副本,就算被多个线程同时访问,也不想重复创建。
为此,我们尝试设计一个缓存中间件 Cacher :
import threading
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
Cacher 内部用一个 dict 对象来缓存已创建的 Data 副本,并提供 get 方法用于获取应用数据 Data 。get 方法获取数据时先查缓存字典,如果数据已存在,便直接将其返回;如果数据不存在,则创建一个并保存到字典中。因此,数据首次被创建后就进入缓存字典,后续如有其它线程同时访问,使用的都是缓存中的同一个副本。
感觉非常不错!但美中不足的是:Cacher 有资源泄露的风险!
因为 Data 一旦被创建后,就保存在缓存字典中,永远都不会释放!换句话讲,程序的资源比如内存,会不断地增长,最终很有可能会爆掉。因此,我们希望一个数据等所有线程都不再访问后,能够自动释放。
我们可以在 Cacher 中维护数据的引用次数, get 方法自动累加这个计数。于此同时提供一个 remove 新方法用于释放数据,它先自减引用次数,并在引用次数降为零时将数据从缓存字段中删除。
线程调用 get 方法获取数据,数据用完后需要调用 remove 方法将其释放。Cacher 相当于自己也实现了一遍引用计数法,这也太麻烦了吧!Python 不是内置了垃圾回收机制吗?为什么应用程序还需要自行实现呢?
冲突的主要症结在于 Cacher 的缓存字典:它作为一个中间件,本身并不使用数据对象,因此理论上不应该对数据产生引用。那有什么黑科技能够在不产生引用的前提下,找到目标对象吗?我们知道,赋值都是会产生引用的!
这时,弱引用( weakref )隆重登场了!弱引用是一种特殊的对象,能够在不产生引用的前提下,关联目标对象。
>>> d = Data('fasionchan.com')
<__main__.Data object at 0x1018571f0>
>>> import weakref
>>> r = weakref.ref(d)
<__main__.Data object at 0x1018571f0>
>>> r() is d
>>> del d
这样一来,我们只需将 Cacher 缓存字典改成保存弱引用,问题便迎刃而解!
import threading
import weakref
class Cacher:
def __init__(self):
self.pool = {}
self.lock = threading.Lock()
def get(self, key):
with self.lock:
r = self.pool.get(key)
if r:
data = r()
if data:
return data
data = Data(key)
self.pool[key] = weakref.ref(data)
return data
由于缓存字典只保存 Data 对象的弱引用,因此 Cacher 不会影响 Data 对象的引用计数。当所有线程都用完数据后,引用计数就降为零因而被释放。
实际上,用字典缓存数据对象的做法很常用,为此 weakref 模块还提供了两种只保存弱引用的字典对象:
weakref.WeakKeyDictionary ,键只保存弱引用的映射类(一旦键不再有强引用,键值对条目将自动消失);
weakref.WeakValueDictionary ,值只保存弱引用的映射类(一旦值不再有强引用,键值对条目将自动消失);
因此,我们的数据缓存字典可以采用 weakref.WeakValueDictionary 来实现,它的接口跟普通字典完全一样。这样我们不用再自行维护弱引用对象,代码逻辑更加简洁明了:
import threading
import weakref
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
weakref 模块还有很多好用的工具类和工具函数,具体细节请参考官方文档,这里不再赘述。
那么,弱引用到底是何方神圣,为什么会有如此神奇的魔力呢?接下来,我们一起揭下它的面纱,一睹真容!
>>> d = Data('fasionchan.com')
>>> from weakref import ref
<class 'weakref'>
>>> r = ref(d)
<weakref at 0x1008d5b80; to 'Data' at 0x100873d60>
经过前面章节,我们对阅读内建对象源码已经轻车熟路了,相关源码文件如下:
Include/weakrefobject.h 头文件包含对象结构体和一些宏定义;
Objects/weakrefobject.c 源文件包含弱引用类型对象及其方法定义;
我们先扒一扒弱引用对象的字段结构,定义于 Include/weakrefobject.h 头文件中的第 10-41 行:
typedef struct _PyWeakReference PyWeakReference;