11

假设我有这个功能(假设我以线程安全的方式访问缓存):

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 中然后在操作完成后将其删除来滚动我自己,但这似乎是一个 hack。

4

5 回答 5

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];
    }
}
于 2012-12-28T17:31:28.263 回答
1

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

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

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

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

在此处输入图像描述

于 2012-12-29T12:05:09.530 回答
0

在这种情况下,我使用互斥锁作为:

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 的缓存,那么互斥锁就是锁定计算机中的所有线程和池,在这种情况下我也使用一个静态随机整数,我将它添加到密钥中,而不是使每个密钥的锁不同,而且每个池也不同。
于 2012-12-28T23:41:59.360 回答
0

将您的锁定移动到您的评论所在的位置。我认为您需要维护当前正在执行的长时间运行操作的列表,并锁定对该列表的访问,并且仅GetValueForIdid您要查找的操作不在该列表中时才执行。我会试着做点什么。

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

仍然存在线程在等待另一个线程获取值时将要做什么的问题。

于 2012-12-28T17:17:39.023 回答
-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];
}
于 2012-12-28T17:17:53.857 回答