我在非常流行的LazyCache库中发现了一些多线程代码,它们使用int[]
字段作为细粒度锁定机制,目的是防止同时调用具有相同key
作为参数的方法.我高度怀疑这个代码的正确性,因为在离开保护区时没有使用Interlocked
或Volatile
操作.以下是代码的重要部分:
private readonly int[] keyLocks;
public virtual T GetOrAdd<T>(string key, Func<ICacheEntry, T> addItemFactory,
MemoryCacheEntryOptions policy)
{
/* Do stuff */
object cacheItem;
// acquire lock per key
uint hash = (uint)key.GetHashCode() % (uint)keyLocks.Length;
while (Interlocked.CompareExchange(ref keyLocks[hash], 1, 0) == 1) Thread.Yield();
try
{
cacheItem = CacheProvider.GetOrCreate<object>(key, CacheFactory);
}
finally
{
keyLocks[hash] = 0;
}
/* Do more stuff */
}
受保护的方法调用是CacheProvider.GetOrCreate<object>(key, CacheFactory)
.它应该一次由一个线程调用,用于相同的key
.为了进入保护区域,有while
个循环,它使用Interlocked.CompareExchange
将keyLocks
数组的值从0
改变为1
.到目前一切尚好.让我担心的是离开保护区的那条线:keyLocks[hash] = 0;
.因为那里没有障碍,我的理解是C#编译器和.NET抖动可以自由地向任何方向移动指令,跨过这条线.因此,CacheProvider.GetOrCreate
方法中的指令可以移动到keyLocks[hash] = 0;
之后.
My question is:根据规范,上面的代码真的确保CacheProvider.GetOrCreate
不会用相同的密钥并发调用吗?这部法典兑现了互斥的promise 吗?还是代码有问题?
Context:相关代码被添加到库中的拉取请求中:Optimize cache to lock per key.