应用数据 Data 由一个 key 唯一标识,同一个数据可能被多个线程同时访问。由于 Data 需要占用很多系统资源,创建和消费的成本很高。我们希望 Data 在程序中只维护一个副本,就算被多个线程同时访问,也不想重复创建。
为此,我们尝试设计一个缓存中间件 Cacher :
import threading # 数据缓存 classCacher: def__init__(self): self.pool = {} self.lock = threading.Lock() defget(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 不是内置了垃圾回收机制吗?为什么应用程序还需要自行实现呢?
# 创建一个数据 >>> d = Data('fasionchan.com') >>> d <__main__.data style="color: #d19a66;line-height: 26px;">0x1018571f0>
# 创建一个指向该数据的弱引用 >>> import weakref >>> r = weakref.ref(d)
# 调用弱引用对象,即可找到指向的对象 >>> r() <__main__.data style="color: #d19a66;line-height: 26px;">0x1018571f0> >>> r() is d True
# 删除临时变量d,Data对象就没有其他引用了,它将被回收 >>> del d # 再次调用弱引用对象,发现目标Data对象已经不在了(返回None) >>> r()
这样一来,我们只需将 Cacher 缓存字典改成保存弱引用,问题便迎刃而解!
import threading import weakref # 数据缓存 classCacher: def__init__(self): self.pool = {} self.lock = threading.Lock()
defget(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 对象的引用计数。当所有线程都用完数据后,引用计数就降为零因而被释放。
/* PyWeakReference is the base struct for the Python ReferenceType, ProxyType, * and CallableProxyType. */ #ifndef Py_LIMITED_API struct _PyWeakReference { PyObject_HEAD
/* The object to which this is a weak reference, or Py_None if none. * Note that this is a stealth reference: wr_object's refcount is * not incremented to reflect this pointer. */ PyObject *wr_object;
/* A callable to invoke when wr_object dies, or NULL if none. */ PyObject *wr_callback;
/* A cache for wr_object's hash code. As usual for hashes, this is -1 * if the hash code isn't known yet. */ Py_hash_t hash;
/* If wr_object is weakly referenced, wr_object has a doubly-linked NULL- * terminated list of weak references to it. These are the list pointers. * If wr_object goes away, wr_object is set to Py_None, and these pointers * have no meaning then. */ PyWeakReference *wr_prev; PyWeakReference *wr_next; }; #endif