当valueFactory有副作用时,ConcurrentDictionary.GetOrAdd

时间:2014-07-23 19:49:46

标签: c# database multithreading concurrency concurrentdictionary

我试图通过为某些非常核心的函数引入缓存层来从我的数据库服务器卸载工作,这些函数将值插入数据库中的表并检索id。这是一个多线程环境。

我的第一个方法是:

public class Cache {
      private Dictionary<string, Int64> i;

      public void Init() { /* init i with values from DB */ }

      public Int64 Get(string value)
         lock(i) {
            Int64 id;
            if (cache.i.TryGetValue(value, out id))
                return id;

            id = /* Insert to DB and retrieve ID */
            cache.i[value] = id;
            return id;
      }
 }

这有帮助。然而,线程仍然相互等待很多。我想减少这段等待时间。我的第一个想法是使用ConcurrentDictionary.GetOrAdd(key, valueFactory)。这不起作用,因为可以多次调用valueFactory。

我已经采用了这种方法:

public class Cache
{
    private ConcurrentDictionary<string, Int64> i;

    public void Init() { /* init i with values from DB */ }

    public Int64 Get(string value)
    {
        Int64 id;
        if (i.TryGetValue(value, out id))
            return id;

        lock (i)
        {
            if (i.TryGetValue(value, out id))
                return id;

            id = /* Insert to DB and retrieve ID */
            i.TryAdd(value, id);
            return id;
        }
    }

有更好的方法吗?这是否是线程安全的?

2 个答案:

答案 0 :(得分:11)

您尝试做的是懒洋洋地创建一个对象,该对象需要创建不超过一次,然后在创建后由任意数量的线程访问。 Lazy就是为此而设计的:

public class Cache
{
    private ConcurrentDictionary<string, Lazy<long>> i;

    public void Init() { /* init i with values from DB */ }

    public Int64 Get(string value)
    {
        return i.GetOrAdd(value, new Lazy<long>(() =>
            CreateDatabaseRecordAndGetId()))
            .Value;
    }

    private long CreateDatabaseRecordAndGetId()
    {
        throw new NotImplementedException();
    }
}

答案 1 :(得分:0)

仅供参考,在Servy的示例中,您获得了为Lazy的每次通话创建的GetOrAdd个实例。现在,Lazy的神奇之处仍然存在,您只需拨打一个来创建您的实例的Func。但也许上面例子中Lazy的额外实例解释了你在尝试时看到的内存增加。

如果您创建&#34; double&#34; lambda,你不会得到Lazy的多个实例。

E.g。将其粘贴到控制台应用中,并使用以下x => new Lazy...比较实施情况:

public static class LazyEvaluationTesting
{
    private static readonly ConcurrentDictionary<int, CustomLazy<CacheableItem>>
        cacheableItemCache = new ConcurrentDictionary<int, CustomLazy<CacheableItem>>();

    private static CacheableItem RetrieveCacheableItem(int itemId)
    {
        Console.WriteLine("--RETRIEVE called\t ItemId [{0}] ThreadId [{1}]", itemId, Thread.CurrentThread.ManagedThreadId);
        return new CacheableItem
        {
            ItemId = itemId
        };
    }

    private static void GetCacheableItem(int itemId)
    {
        Console.WriteLine("GET called\t ItemId [{0}] ThreadId [{1}]", itemId, Thread.CurrentThread.ManagedThreadId);

        CacheableItem cacheableItem = cacheableItemCache
            .GetOrAdd(itemId,
                x => new CustomLazy<CacheableItem>(
                    () => RetrieveCacheableItem(itemId)
                )
            ).Value;

        //CacheableItem cacheableItem2 = cacheableItemCache
        //  .GetOrAdd(itemId,
        //      new CustomLazy<CacheableItem>(
        //          () => RetrieveCacheableItem(itemId)
        //      )
        //  ).Value;
    }

    public static void TestLazyEvaluation()
    {
        int[] itemIds = { 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 };
        ParallelOptions options = new ParallelOptions
        {
            MaxDegreeOfParallelism = 75
        };

        Parallel.ForEach(itemIds, options, itemId =>
        {
            GetCacheableItem(itemId);
            GetCacheableItem(itemId);
            GetCacheableItem(itemId);
            GetCacheableItem(itemId);
            GetCacheableItem(itemId);
        });
    }

    private class CustomLazy<T> : Lazy<T> where T : class
    {
        public CustomLazy(Func<T> valueFactory)
            : base(valueFactory)
        {
            Console.WriteLine("-Lazy Constructor called  ThreadId [{0}]", Thread.CurrentThread.ManagedThreadId);
        }
    }

    private class CacheableItem
    {
        public int ItemId { get; set; }
    }
}

来源:Reed Copsey's Blog