zoukankan      html  css  js  c++  java
  • 改进ConcurrentDictionary并行使用的性能

    改进ConcurrentDictionary并行使用的性能

    上一篇文章“ConcurrentDictionary 对决 Dictionary+Locking”中,我们知道了 .NET 4.0 中提供了线程安全的 ConcurrentDictionary<TKey, TValue> 类型,并在某些特定的使用条件下会产生问题。

    在 ConcurrentDictionary<TKey, TValue> 类中有一个方法 GetOrAdd ,用于尝试获取一个键值,如果键值不存在则添加一个。其方法签名如下:

    复制代码
    public TValue GetOrAdd(
      TKey key,
      Func<TKey, TValue> valueFactory
    )
    
    Parameters
    key
      Type: TKey
      The key of the element to add.
    
    valueFactory
      Type: System.Func<TKey, TValue>
      The function used to generate a value for the key
    复制代码

    通常,我们会通过如下这种方式来使用:

    复制代码
          ConcurrentDictionary<string, ExpensiveClass> dict1
            = new ConcurrentDictionary<string, ExpensiveClass>();
    
          string key1 = "111111";
          ExpensiveClass value1 = dict1.GetOrAdd(
            key1, 
            (k) => new ExpensiveClass(k));
    复制代码

    这种使用方式会产生一个问题,就是如果特定的类的构造过程比较昂贵(资源消耗、时间消耗等),在并行运行条件下,当第一个线程尝试获取该键值时,发现不存在后开始构建该对象,而在构建的同时,另外一个线程也尝试获取该键值,发现不存在后也开始构建该对象,当第一个线程构造完毕后将对象添加至字典中,而第二个对象也构造完毕后会再次检测字典中是否存在该键值,因为键值已经存在,所以将刚创建完毕的对象直接丢弃,而使用已存在的对象,这造成了对象构造过程中的浪费。如果是关注性能和资源的应用,此处就是一个需要改进的点。

    我们假设这个类叫 ExpensiveClass 。

    复制代码
      public class ExpensiveClass
      {
        public ExpensiveClass(string id)
        {
          Id = id;
    
          Console.WriteLine(
            "Id: [" + id + "] called expensive methods " +
            "which perhaps consume a lot of resources or time.");
        }
    
        public string Id { get; set; }
      }
    复制代码

    类实例化的构造过程为什么昂贵可能有很多中情况,最简单的例子可以为:

    • 访问了数据库,读取了数据,并缓存了数据。
    • 访问了远程服务,读取了数据,并缓存了数据。
    • 将磁盘中的数据加载到内存中。

    改进方式1:使用Proxy模式

    我们可以使用 Proxy 模式来包装它,通过 Proxy 中间的代理过程来隔离对对象的直接创建。

    复制代码
     1   public class ExpensiveClassProxy
     2   {
     3     private string _expensiveClassId;
     4     private ExpensiveClass _expensiveClass;
     5 
     6     public ExpensiveClassProxy(string expensiveClassId)
     7     {
     8       _expensiveClassId = expensiveClassId;
     9     }
    10 
    11     public ExpensiveClass XXXMethod()
    12     {
    13       if (_expensiveClass == null)
    14       {
    15         lock (_expensiveClass)
    16         {
    17           if (_expensiveClass == null)
    18           {
    19             _expensiveClass = new ExpensiveClass(_expensiveClassId);
    20           }
    21         }
    22       }
    23       return _expensiveClass;
    24     }
    25   }
    复制代码

    改进方式2:使用Lazy<T>模式

    这种方式简单易用,并且同样解决了问题。

    复制代码
    1       ConcurrentDictionary<string, Lazy<ExpensiveClass>> dict2
    2         = new ConcurrentDictionary<string, Lazy<ExpensiveClass>>();
    3 
    4       string key2 = "222222";
    5       ExpensiveClass value2 = dict2.GetOrAdd(
    6         key2,
    7         (k) => new Lazy<ExpensiveClass>(
    8           () => new ExpensiveClass(k)))
    9         .Value;
    复制代码

    在并行的条件下,同样也存在构造了一个 Lazy<ExpensiveClass> 然后丢弃的现象,所以这种方式是建立在,构造 Lazy<T> 对象的成本要小于构造 ExpensiveClass 的成本。

     
     
  • 相关阅读:
    webstorm
    呐,这是某蒟蒻幼稚的博客 ~~Welcome
    CSP-S 2021 补题记录
    CSP-S 2021 游记
    Tarjan 算法小结
    FHQ Treap 浅析
    2048游戏 (C++ Windows)
    线段树 算法分析
    树状数组 算法分析
    数学期望(ξ) 浅析
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3145144.html
Copyright © 2011-2022 走看看