ConcurrentDictionary最后.当valueFactory有副作用时GetOrAdd

本文关键字:副作用 GetOrAdd valueFactory 最后 ConcurrentDictionary | 更新日期: 2023-09-27 18:14:22

我试图通过为一些非常核心的函数引入缓存层来卸载数据库服务器上的工作,这些函数将值插入数据库中的表并检索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;
        }
    }

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

ConcurrentDictionary最后.当valueFactory有副作用时GetOrAdd

您要做的是惰性地创建一个对象,该对象只需要创建一次,然后在创建后由任意数量的线程访问。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();
    }
}

仅供参考,在Servy的示例中,您可以为每次调用GetOrAdd创建Lazy的实例。现在,Lazy的魔力仍然在发生,并且您只需要调用一次创建实例的Func。但是,也许上面例子中Lazy的额外实例可以解释当您尝试它时看到的内存增加。

如果你创建一个"double" lambda,你不会得到Lazy的多个实例化。

。将其粘贴到控制台应用程序中,并比较使用和不使用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