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

时间:2012-12-28 17:14:07

标签: c# multithreading locking

假设我有这个功能(假设我以线程安全的方式访问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中然后在操作完成后删除它来自行滚动,但这看起来像是一个黑客。

5 个答案:

答案 0 :(得分:7)

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

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

Lazy<object> GetCachedValue(string id)
{
    lock (Cache)
    {
        if (!Cache.ContainsKey(id))
        {
            Lazy<object> lazy = new Lazy<object>(() =>
                {
                    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];
    }
}

答案 1 :(得分:1)

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

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

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

我对SyncDispatcher的实现是这样的:

public class SyncDispatcher : IDisposable
{
    private static object _lock = new object();
    private static Dictionary<object, SyncDispatcher> _container = new Dictionary<object, SyncDispatcher>();

    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);              
    }
}

enter image description here

答案 2 :(得分:0)

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

private List<string> m_runningCacheIds = new List<string>();

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];
}

当线程在另一个线程上等待获取值时,仍然存在线程将要执行的操作的问题。

答案 3 :(得分:0)

我在那种情况下使用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的缓存,那么互斥锁会锁定计算机中的所有线程和池,在这种情况下我还使用一个静态随机整数,我将其添加到密钥,而不是为每个密钥而不是每个池使锁不同。

答案 4 :(得分:-2)

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

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];
}