C#:使用共享资源的最佳方式?

C#:使用共享资源的最佳方式?,c#,multithreading,C#,Multithreading,我需要序列化对共享资源(即缓存)的访问。我使用后面描述的模式,但有时,特别是前两个线程,会加载两次数据。问题在哪里 public class Entity { } public class CacheManager { public static CacheManager Instance = new CacheManager(); private CacheManager() { _thisLock = new Object(); _

我需要序列化对共享资源(即缓存)的访问。我使用后面描述的模式,但有时,特别是前两个线程,会加载两次数据。问题在哪里

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;
    }
}
公共类实体{}
公共类缓存管理器
{
公共静态CacheManager实例=新建CacheManager();
专用缓存管理器()
{
_thisLock=新对象();
_cache=新字典();
}
私有只读对象_thisLock;
专用只读IDictionary\u缓存;
公共实体GetEntity(int-id)
{
实体;
if(!\u cache.TryGetValue(id,out实体)){
/*一次只能有一个线程进入lock语句。
*因此,如果线程[1]和[2]同时到达这里,.NET应该将线程[1]传递到内部,[2]等待*/
锁(这个锁){
如果(!_cache.TryGetValue(id,out entity)){/*如果我们是[2]:检查[1]是否对我们有效(并从缓存加载)*/
/*我们是[1],所以让我们从存储库加载实体并将其添加到缓存中*/
实体=新实体();//模拟存储库访问
_cache.Add(id,entity);
}
}
}
返回实体;
}
}
为什么两个线程都在lock语句中执行步骤? 是因为我有四核处理器吗? 如果我在lock语句之前添加“Thread.Sleep(1);”,则一切正常。 我应该使用互斥类吗


谢谢。

我建议在if语句之前添加锁。

我建议在if语句之前添加锁。

您的代码不是线程安全的。由于您没有锁定第一个TryGetValue,因此线程可能会在修改字典(使用Add)时尝试访问字典。在当前的.Net字典实现中,如果正在修改基础字典,TryGetValue可能会引发异常

您最好完全转储外部TryGetValue,或者使用ReaderWriterLockSlim


要回答您的问题,可能是因为您没有从调用代码访问您的单例(实例)?无论如何,不要使用这种锁定策略,它很脆弱。使用ReaderWriterLockSlim或转储第一个TryGetValue。

您的代码不是线程安全的。由于您没有锁定第一个TryGetValue,因此线程可能会在修改字典(使用Add)时尝试访问字典。在当前的.Net字典实现中,如果正在修改基础字典,TryGetValue可能会引发异常

您最好完全转储外部TryGetValue,或者使用ReaderWriterLockSlim

要回答您的问题,可能是因为您没有从调用代码访问您的单例(实例)?无论如何,不要使用这种锁定策略,它很脆弱。使用ReaderWriterLockSlim或转储第一个TryGetValue。

Feryt, 这里有一个可怕的想法:我只在调试模式下为lock()生成的代码中遇到了公认的MS bug 3.5sp1 CLR(如果您感兴趣,我将尝试查找)。我发现在调试会话期间,我将有两个线程在lock()语句中执行代码——这不太好。您可以通过在lock()中使用try/finally对来验证这一点,该对包含int的递增/递减。然后,如果联锁变量超过1,我将调用debugger.break()。您还可以创建另一个变量,该变量在锁定之前递增,然后在锁定内部立即递减(再次使用联锁增量)。这些变量将显示等待锁的线程数以及lock()语句本身中当前执行代码的线程数 祝你好运

费伊特, 这里有一个可怕的想法:我只在调试模式下为lock()生成的代码中遇到了公认的MS bug 3.5sp1 CLR(如果您感兴趣,我将尝试查找)。我发现在调试会话期间,我将有两个线程在lock()语句中执行代码——这不太好。您可以通过在lock()中使用try/finally对来验证这一点,该对包含int的递增/递减。然后,如果联锁变量超过1,我将调用debugger.break()。您还可以创建另一个变量,该变量在锁定之前递增,然后在锁定内部立即递减(再次使用联锁增量)。这些变量将显示等待锁的线程数以及lock()语句本身中当前执行代码的线程数 祝你好运

那是真的!多大的错误(或隐藏的调试功能:))。在禁用调试模式下,代码按预期工作。我将使用ReaderWriterLockSlim而不是lock()。谢谢你,这是真的!多大的错误(或隐藏的调试功能:))。在禁用调试模式下,代码按预期工作。我将使用ReaderWriterLockSlim而不是lock()。谢谢。我认为字典是线程安全的,但这不是主要问题。我将使用ReaderWriterLockSlim,因为它正是我想要的。但是lock语句中的bug(见下文)很奇怪。我认为字典是线程安全的“内部”,但这不是主要问题。我将使用ReaderWriterLockSlim,因为它正是我想要的。但是lock语句中的bug(见下文)很奇怪。