我需要序列化对共享资源(即Cache)的访问。我使用后面描述的模式,但有时,特别是前两个线程,加载数据两次。问题在哪里?
public class Entity { }
public class CacheManager
{
public static CacheManager Instance = new CacheManager();
private CacheManager()
{
_thisLock = new Object();
_cache = new Dictionary<int, Entity>();
}
private readonly Object _thisLock;
private readonly IDictionary<int, Entity> _cache;
public Entity GetEntity(int id)
{
Entity entity;
if ( !_cache.TryGetValue(id, out entity) ) {
/* Only one thread, at a time, can go inside lock statement.
* So, if threads [1] and [2] came here at the same time, .NET shoud pass thread [1] inside and [2] waits. */
lock ( _thisLock ) {
if ( !_cache.TryGetValue(id, out entity) ) { /* If we are [2] : check(and loads from cache) if [1] did work for us. */
/* we are [1] so let's load entity from repository and add it to cache */
entity = new Entity(); // simulate repository access
_cache.Add(id, entity);
}
}
}
return entity;
}
}
为什么两个线程都在lock语句中执行? 是因为我有四核处理器吗? 如果我添加“Thread.Sleep(1);”在锁定声明之前,一切正常。 我应该使用Mutex类吗?
谢谢。
答案 0 :(得分:3)
您的代码不是线程安全的。由于您没有锁定第一个TryGetValue,因此线程可能会在修改字典时尝试访问该字典(使用Add)。在当前的.Net Dictionary实现中,如果正在修改基础字典,TryGetValue可能会引发异常。
您最好完全转储外部TryGetValue,或者更好地使用ReaderWriterLockSlim。
要回答您的问题,可能是因为您没有从调用代码访问您的单例(实例)?无论如何,不要使用这种锁定策略,它很脆弱。使用ReaderWriterLockSlim或转储第一个TryGetValue。
答案 1 :(得分:1)
Feryt, 这是一个可怕的想法:我在调试模式中为lock()生成的代码中遇到了一个已确认的MS错误3.5sp1 CLR(如果您感兴趣,我会尝试找到它)。我发现在调试会话期间,我将有两个线程在lock()语句中执行代码 - 不好。您可以通过在lock()中使用包含interlocked递增/递减的try / finally对来验证这一点。然后,如果该互锁变量超过1,我将调用debugger.break()。您还可以创建另一个在锁定之前递增的变量,然后在锁定内部立即递减(再次使用互锁增量)。这些变量将显示等待锁的线程数以及当前在lock()语句本身内执行代码的线程数 祝你好运!
答案 2 :(得分:0)
我建议在if语句之前添加锁 。