我想创建一个缓存,这样当一个项目不存在时,只有一个请求该项目的人花时间生成它,而其他人同时请求它将直接阻塞,直到结果缓存为第一个人。以下是该场景的描述:
我原以为我会在ConcurrentDictionary中使用DateTime来存储日期作为关键字,如ConcurrentDictionary<DateTime, CustomImmutableObject> _cache;
但是我无法看到线程2和3如何能够等待某些东西。即使存在另一个存储某种状态标志的ConcurrentDictionary,它们如何知道线程1何时完成?
有没有人对如何开发此类缓存有任何建议?
答案 0 :(得分:4)
您可以使用ConcurrentDictionary<DateTime, Lazy<CustomImmutableObject>>
获取对象,如下所示:
myDictionary
.GetOrAdd(
someDateTime,
dt => new Lazy<CustomImmutableObject>(
() => CreateMyCustomImmutableObject()
)
).Value
Lazy<T>
在(默认)线程安全模式下所做的保证将确保初始化仅发生一次,并且在实际实例化的值之前的后续访问将阻止。
在多线程方案中,第一个访问线程安全的Lazy(Of T)对象的Value属性的线程会为所有线程上的所有后续访问初始化它,并且所有线程共享相同的数据。因此,哪个线程初始化对象并且竞争条件是良性的并不重要。
有关详细信息,请参阅here。
下面是我写的一个测试,以确保我不会喷出。这应该能说服你一切都好:
void Main()
{
var now=DateTime.UtcNow;
var d=new ConcurrentDictionary<DateTime, Lazy<CustomImmutableObject>>();
Action f=()=>{
var val=d
.GetOrAdd(
now,
dt => new Lazy<CustomImmutableObject>(
() => new CustomImmutableObject()
)
).Value;
Console.WriteLine(val);
};
for(int i=0;i<10;++i)
{
(new Thread(()=>f())).Start();
}
Thread.Sleep(15000);
Console.WriteLine("Finished");
}
class CustomImmutableObject
{
public CustomImmutableObject()
{
Console.WriteLine("CREATING");
Thread.Sleep(10000);
}
}
答案 1 :(得分:0)
每次有人访问缓存对象时都可以放置lock
,如果有缓存未命中,请在某个对象上执行Monitor.Enter
以指示正在创建对象,然后释放第一个锁。创建完成后,在第二个锁定对象上使用Monitor.Exit
。
缓存访问通常会锁定主对象,但创建会锁定第二个。如果您希望创建并行发生,则可以在字典中创建一个锁定对象,其中键是缓存的相同键。
答案 2 :(得分:0)
我提出了以下似乎有效的方法,但代价是锁定每次读取缓存。假设只有一个人可以添加到cacheData是安全的 对于一次给定的密钥?
static ConcurrentDictionary<DateTime, object> cacheAccess = new ConcurrentDictionary<DateTime, object>();
static ConcurrentDictionary<DateTime, int> cacheData = new ConcurrentDictionary<DateTime, int>();
static int GetValue(DateTime key)
{
var accessLock = cacheAccess.GetOrAdd(key, x => new object());
lock (accessLock)
{
int resultValue;
if (!cacheData.TryGetValue(key, out resultValue))
{
Console.WriteLine("Generating {0}", key);
Thread.Sleep(5000);
resultValue = (int)DateTime.Now.Ticks;
if (!cacheData.TryAdd(key, resultValue))
{
throw new InvalidOperationException("How can something else have added inside this lock?");
}
}
return resultValue;
}
}
static void Main(string[] args)
{
var keys = new[]{ DateTime.Now.Date, DateTime.Now.Date.AddDays(-1), DateTime.Now.Date.AddDays(1), DateTime.Now.Date.AddDays(2)};
var rand = new Random();
Parallel.For(0, 1000, (index) =>
{
var key = keys[rand.Next(keys.Length)];
var value = GetValue(key);
Console.WriteLine("Got {0} for key {1}", value, key);
});
}
答案 3 :(得分:0)
通常情况下会这样:
public static object GetItem(DateTime dt)
{
// Check to see if the cache already has my item
if (_dictionary.ContainsKey(dt))
return _dictionary[dt];
lock (_lock)
{
// Check the cache AGAIN in case a previous thread inserted our value
if (_dictionary.ContainsKey(dt))
return _dictionary[dt];
// Add the generate object to the dictionary
_dictionary.Add(dt, GenerateMyObject(dt));
}
}
private static Dictionary<DateTime, object> _dictionary = new Dictionary<DateTime, object>();
private static object _lock = new object();
只要你重新检查你在锁内找到的任何对象的缓存,你应该没问题。