锁定和刷新实现

时间:2015-03-31 01:32:48

标签: c# multithreading caching locking

我有一个任务映射的密钥,我只有在给定的任务尚未运行时才需要运行任务。伪代码如下。我相信还有很大的改进空间。我在地图上锁定,因此几乎序列化了对CacheFreshener的访问。有没有更好的方法呢?我们知道,当我试图锁定密钥k1时,缓存清新器调用密钥k2等待锁定是没有意义的。

class CacheFreshener
{
     private ConcurrentDictionary<string,bool> lockMap;

     public RefreshData(string key, Func<string, bool> cacheMissAction)
     {
         lock(lockMap)
         {
             if (lockMap.ContainsKey(key))
             {
                 // no-op
                 return;
             }
             else
             {
                lockMap.Add(key, true); 
             }  
         }

         // if you are here means task is not already present
         cacheMissAction(key);

         lock(lockMap) // Do we need to lock here??
         {
             lockMap.Remove(key);
         }  
     }  

}

1 个答案:

答案 0 :(得分:0)

根据要求,这里是对我的评论相关的详细说明......

这里的基本问题似乎是并发问题,即一次访问同一个对象的两个或多个线程。这是为ConcurrentDictionary设计的方案。如果分别使用IDictionaryContainsKey()的{​​{1}}方法,那么您需要显式同步(但仅适用于该操作...在此特定方案中,在调用时不会严格需要Add())确保将它们作为单个原子操作执行。但是Remove()类预测到了这种需求,并且包含ConcurrentDictionary方法来完成相同的操作,而没有显式同步。

<强>&LT;一旁&GT;
我不完全清楚代码示例背后的意图。该代码似乎仅用于在TryAdd()委托的调用期间将对象存储在“缓存”中。密钥在之后立即删除。所以看起来它本身并没有真正缓存任何东西。它只是阻止多个线程一次在调用cacheMissAction的过程中(后续线程将无法调用它,但也不能指望它在调用{{1}时完成它方法已经完成) 的&LT; /预留&GT;

但是以给定的代码示例为例,很明显实际上不需要显式锁定。 cacheMissAction类已经提供了线程安全访问(即,当从多个线程并发使用时,数据结构不会损坏),它提供RefreshData()方法作为添加密钥的机制(及其值虽然在这里只是字典的ConcurrentDictionary字面值TryAdd(),但它确保一次只有一个字符在字典中有一个键。

因此,我们可以将代码重写为这样,并实现相同的目标:

bool

添加或删除都不需要true语句,因为private ConcurrentDictionary<string,bool> lockMap; public RefreshData(string key, Func<string, bool> cacheMissAction) { if (!lockMap.TryAdd(key, true)) { return; } // if you are here means task was not already present cacheMissAction(key); lockMap.Remove(key); } 会自动处理整个“检查密钥并添加(如果不存在)”操作。


我会注意到使用字典来完成集合的工作可能被认为是低效的。如果集合可能不是很大,那没什么大不了的,但我觉得奇怪的是,微软选择犯下他们最初制作时的错误,因为在使用非泛型字典对象的前仿制时代{在lock出现之前,{1}}存储一个集合。现在我们在TryAdd()中拥有所有这些易于使用的类,但在那里没有Hashtable的线程安全实现。叹息...

那就是说,如果你更喜欢在存储方面更有效的方法(这不一定是更快的实现,取决于的并发访问模式对象),这样的东西可以替代:

HashSet<T>

在这种情况下,您确实需要System.Collections.Concurrent语句,因为ISet<T>类本质上不是线程安全的。这当然与原始实现非常相似,只是使用private HashSet<string> lockSet; private readonly object _lock = new object(); public RefreshData(string key, Func<string, bool> cacheMissAction) { lock (_lock) { if (!lockSet.Add(key)) { return; } } // if you are here means task was not already present cacheMissAction(key); lock (_lock) { lockSet.Remove(key); } } 更像集合的语义。