2

我试图在脑海中推理出如何使用 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导致值在保留时已被释放通话。所以这种方法不会按原样工作。也可能有其他问题。

有没有人已经完成了类似的工作并愿意分享?或者有一个指向野外类似事物的指针?

4

1 回答 1

1

我最终采用了这种方法来解决这个问题:Read-Copy-Update。它似乎工作得很好——读取性能比基于锁的方法快 50 倍(但这并不奇怪。)

于 2013-06-10T14:01:50.627 回答