在.NET环境中,垃圾回收器(GC)是一个强大的后台服务,它自动管理内存,回收不再使用的资源。然而,GC的这种能力往往被开发者忽视。本文将探讨如何利用GC的这种能力,设计一个高效的TTL(Time-to-Live)缓存系统。
设想一个系统,其中客户端与对等体(peer)进行通信。每个客户端到对等体的消息都需要一个连接。建立连接是一个重量级操作,希望缓存打开的连接。当特定连接的所有消息都发送完毕后,连接应该在一段时间后关闭。
典型的使用案例如下:
实现上述所有内容感觉并不正确。感觉像是在重新发明垃圾回收器。不能直接使用.NET提供的垃圾回收器吗?
目标是:一个GC感知的TTL缓存。
C# 接口定义如下:
public interface ITtlCache
TTL缓存的主要目的是将与垃圾回收器的交互和生存时间(TTL)问题封装起来,使其不会污染自己的缓存组件的设计和实现。
以下是这样一个缓存应该封装的易变性:
还有一个易变性可能已经被封装了。这是将对象放入缓存和释放死亡对象的竞态条件。但经过思考,最终确信这种易变性属于TTL缓存的外部范围。
以下是TTL缓存的静态视图(参与者)。每个参与者附近的封装易变性已列出。
TTL缓存可以被认为是一种映射,将facet(轻量级对象)映射到body(重量级对象)。在系统中,消息是连接的facet。TTL缓存将对象包装在一个TtlItem中,并将其存储在私有存储库ItemsRepo中。然后TTL缓存为每个facet和body创建一个DeathNotifier。DeathNotifier将facet被垃圾回收的事件委托给TtlItem,其中维护了引用计数器。
TTL缓存的方法是线程安全的,可以在任意线程中调用。由于TTL缓存不公开任何事件,其客户端也不需要处理垃圾回收器线程。TTL缓存的状态存储在ItemsRepo类中,该类受到独占锁的保护。
以下是一个基本的单元测试示例:
class Message {}
class Connection {}
[Test]
public void Item_should_be_removed_after_its_death() {
var cache = TtlCache
实现的核心部分是.NET核心类ConditionalWeakTable。它在.NET 4中添加,以帮助编译器为每个对象存储自定义属性。ConditionalWeakTable允许将任何属性(对象)与对等体(另一个对象)关联起来,以便当对等体被垃圾回收器收集时,关联的属性也被收集。实现的中心思想是将一个给定对象与一个带有析构函数的DeathNotifier类关联。因此,当给定对象被垃圾回收器收集时,关联的死亡通知器也被收集,其析构函数被调用。因此,析构函数有效地成为了一个给定对象被收集的事件。
DeathNotifier的本质如下:
class DeathNotifier {
private IReferenceCounter _reference;
~DeathNotifier() {
var reference = _reference;
if (reference != null)
reference.ReleaseBody();
}
}
任何给定对象都被内部的TtlItem包装,它实现了两个接口ITtlItem
和IReferenceCounter。
TtlItem实现引用计数和死亡逻辑如下:
class TtlItem : ITtlItem, IReferenceCounter where TBody : class {
void IReferenceCounter.LinkBody() {
int count = Interlocked.Increment(ref _referenceCounter);
if (count == 1)
_deadSince = DateTime.MaxValue;
}
void IReferenceCounter.ReleaseBody() {
int count = Interlocked.Decrement(ref _referenceCounter);
if (count == 0)
_deadSince = DateTime.UtcNow;
}
}