我有一个任务映射的密钥,我只有在给定的任务尚未运行时才需要运行任务。伪代码如下。我相信还有很大的改进空间。我在地图上锁定,因此几乎序列化了对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);
}
}
}
答案 0 :(得分:0)
根据要求,这里是对我的评论相关的详细说明......
这里的基本问题似乎是并发问题,即一次访问同一个对象的两个或多个线程。这是为ConcurrentDictionary
设计的方案。如果分别使用IDictionary
和ContainsKey()
的{{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);
}
}
更像集合的语义。