我试图在脑海中推理出如何使用 API 为引用计数值实现线程安全缓存机制,大致如下:(注意:我使用的是 Objective-C 语法,但问题不是特定于语言的)
typedef id (^InvalidatingLazyGenerator)();
@interface InvalidatingLazyObject : NSObject
- (id)initWithGenerator: (InvalidatingLazyGenerator)generator;
@property (readonly) id value;
- (void)invalidate;
@end
当有人请求-value
时,如果它有一个现有的缓存值,它应该返回-retain/-autoreleased
该值的一个版本。如果它没有值,或者该值无效,它应该使用在初始化时传入的生成块生成一个,然后它应该缓存该值以供将来读取,直到有人调用-invalidate
.
假设我们不关心生成器块是否被多次调用(即第二个读取器到达,而第一个读取器在生成器块中),只要它返回的对象在发生这种情况时没有泄漏。第一次通过,非无等待的实现可能看起来像:
- (id)value
{
id retVal = nil;
@synchronized(self)
{
retVal = [mValue retain];
}
if (!retVal)
{
retVal = [[mGenerator() retain] retain]; // Once for the ivar and once for the return value
id oldVal = nil;
@synchronized(self)
{
oldVal = mValue;
mValue = retVal;
}
[oldVal release];
}
return [retVal autorelease];
}
- (void)invalidate
{
id val = nil;
@synchronized(self)
{
val = mValue;
mValue = nil;
}
[val release];
}
自然,这会导致糟糕的读取性能,因为并发读取是由锁序列化的。读取器/写入器锁改善了这一点,但在读取路径中仍然很慢。这里的性能目标是使缓存读取尽可能快(希望无锁)。如果我们必须计算一个新值,读取速度慢是可以的,-invalidate
速度慢也是可以的。
所以......我试图找出一种方法来使读取锁定/等待免费。我的第一个想法(有缺陷 - 见下文)涉及添加一个失效计数器,其值是原子的、单调递增的并使用内存屏障读取。它看起来像这样:
- (id)value
{
// I think we don't need a memory barrier before this first read, because
// a stale read of the count can only cause us to generate a value unnecessarily,
// but should never cause us to return a stale value.
const int64_t startCount = mWriteCount;
id retVal = [mValue retain];
OSMemoryBarrier(); // But we definitely want a "fresh" read here.
const int64_t endCount = mWriteCount;
if (retVal && startCount == endCount)
{
return [retVal autorelease];
}
// Now we're in the slow path
retVal = [mGenerator() retain]; // we assume generator has given us an autoreleased object
@synchronized(self)
{
mValue = retVal;
OSAtomicIncrement64Barrier(&mWriteCount);
}
return retVal;
}
- (void)invalidate
{
id value = nil;
@synchronized(self)
{
value = mValue;
mValue = nil;
OSAtomicIncrement64Barrier(&mWriteCount);
}
[value release];
}
但我已经在这里看到了问题。例如,[mValue retain]
在读取路径中:我们需要保留值,但是在读取mValue
和调用-retain
另一个线程之间的时间可能会-invalidate
导致值在保留时已被释放通话。所以这种方法不会按原样工作。也可能有其他问题。
有没有人已经完成了类似的工作并愿意分享?或者有一个指向野外类似事物的指针?