防止两个线程进入具有相同值的代码块

假设我有这个function(假设我以线程安全的方式访问Cache):

object GetCachedValue(string id) { if (!Cache.ContainsKey(id)) { //long running operation to fetch the value for id object value = GetTheValueForId(id); Cache.Add(id, value); } return Cache[id]; } 

我想防止两个线程同时运行“ 长时间运行”操作 相同的值 。 显然我可以将整个东西包装在一个lock()中,但是整个函数会阻塞而不管值是什么,我希望两个线程能够执行长时间运行的操作,只要它们正在寻找不同的id。

是否存在基于值锁定的内置锁定机制,因此一个线程可以在另一个线程完成长时间运行操作时阻塞,因此我不需要执行两次(或N次)? 理想情况下,只要在一个线程中执行长时间运行的操作 ,其他线程就不应该为相同的id值执行它。

我可以通过将id放在HashSet中然后在操作完成后删除它们来自己滚动,但这看起来像是一个黑客。

我会在这里使用Lazy 。 下面的代码将锁定缓存,将Lazy放入缓存并立即返回。 长时间运行操作将以线程安全方式执行一次。

 new Thread(() => Console.WriteLine("1-" + GetCachedValue("1").Value)).Start(); new Thread(() => Console.WriteLine("2-" + GetCachedValue("1").Value)).Start(); 

 Lazy GetCachedValue(string id) { lock (Cache) { if (!Cache.ContainsKey(id)) { Lazy lazy = new Lazy(() => { Console.WriteLine("**Long Running Job**"); Thread.Sleep(3000); return int.Parse(id); }, true); Cache.Add(id, lazy); Console.WriteLine("added to cache"); } return Cache[id]; } } 

在这种情况下,我想有这样的界面

 using (SyncDispatcher.Enter(id)) { //any code here... } 

所以我可以执行任何代码,如果id相同,它将是线程安全的。 如果我需要从Cache获取值,我可以直接进行,因为没有并发调用。

我对SyncDispatcher的实现是这样的:

 public class SyncDispatcher : IDisposable { private static object _lock = new object(); private static Dictionary _container = new Dictionary(); private AutoResetEvent _syncEvent = new AutoResetEvent(true); private SyncDispatcher() { } private void Lock() { _syncEvent.WaitOne(); } public void Dispose() { _syncEvent.Set(); } public static SyncDispatcher Enter(object obj) { var objDispatcher = GetSyncDispatcher(obj); objDispatcher.Lock(); return objDispatcher; } private static SyncDispatcher GetSyncDispatcher(object obj) { lock (_lock) { if (!_container.ContainsKey(obj)) { _container.Add(obj, new SyncDispatcher()); } return _container[obj]; } } } 

简单测试:

 static void Main(string[] args) { new Thread(() => Execute("1", 1000, "Resource 1")).Start(); new Thread(() => Execute("2", 200, "Resource 2")).Start(); new Thread(() => Execute("1", 0, "Resource 1 again")).Start(); } static void Execute(object id, int timeout, string message) { using (SyncDispatcher.Enter(id)) { Thread.Sleep(timeout); Console.WriteLine(message); } } 

在此处输入图像描述

将锁定移动到评论所在的位置。 我认为您需要维护当前正在执行的长时间运行操作的列表,并锁定对该列表的访问,并且只有在您要查找的id不在该列表中时才执行GetValueForId 。 我会尝试鞭打一些东西。

 private List m_runningCacheIds = new List(); object GetCachedValue(string id) { if (!Cache.ContainsKey(id)) { lock (m_runningCacheIds) { if (m_runningCacheIds.Contains(id)) { // Do something to wait until the other Get is done.... } else { m_runningCacheIds.Add(id); } } //long running operation to fetch the value for id object value = GetTheValueForId(id); Cache.Add(id, value); lock (m_runningCacheIds) m_runningCacheIds.Remove(id); } return Cache[id]; } 

还有一个问题就是线程在等待另一个线程时要做的是获取值。

我在那种情况下使用Mutex作为:

 object GetCachedValue(string Key) { // note here that I use the key as the name of the mutex // also here you need to check that the key have no invalid charater // to used as mutex name. var mut = new Mutex(true, key); try { // Wait until it is safe to enter. mut.WaitOne(); // here you create your cache if (!Cache.ContainsKey(Key)) { //long running operation to fetch the value for id object value = GetTheValueForId(Key); Cache.Add(Key, value); } return Cache[Key]; } finally { // Release the Mutex. mut.ReleaseMutex(); } } 

笔记:

  • 某些字符对互斥锁名称无效(如斜杠)
  • 如果您使用的每个应用程序(或Web池)的缓存都不同,并且如果我们代表asp.net的缓存,那么互斥锁会锁定计算机中的所有线程和池,在这种情况下我也会使用一个静态的随机整数,我将它添加到密钥中,并且不会使每个密钥的锁不同,而且每个池的锁也不同。

它不是世界上最优雅的解决方案,但我通过双重检查和锁定解决了这个问题:

 object GetCachedValue(string id) { if (!Cache.ContainsKey(id)) { lock (_staticObj) { if (!Cache.ContainsKey(id)) { //long running operation to fetch the value for id object value = GetTheValueForId(id); Cache.Add(id, value); } } } return Cache[id]; }