改进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 的成本。

 







本文转自匠心十年博客园博客,原文链接:http://www.cnblogs.com/gaochundong/p/concurrent_dictionary_with_lazy.html,如需转载请自行联系原作者

目录
相关文章
|
9月前
|
缓存 安全 Linux
Linux系统查看操作系统版本信息、CPU信息、模块信息
在Linux系统中,常用命令可帮助用户查看操作系统版本、CPU信息和模块信息
1574 23
|
12月前
|
存储 SQL 数据库连接
【QT速成】半小时入门QT6之QT前置知识扫盲(二)
【QT速成】半小时入门QT6之QT前置知识扫盲(二)
372 13
|
12月前
|
存储 SQL 数据库连接
【QT速成】半小时入门QT6之QT前置知识扫盲(二)
【QT速成】半小时入门QT6之QT前置知识扫盲(二)
224 2
|
12月前
|
搜索推荐 安全
如果您干不动跨境外贸独立站,可以来看看反向海淘代购模式
反向海淘代购模式是指海外消费者通过国内电商平台购买中国商品,再由代购方负责采购、质检、包装和国际运输。该模式商品丰富、价格竞争力强,能满足个性化需求,但也面临物流成本高、海关政策复杂等挑战。
|
SQL 关系型数据库 MySQL
MySQL 中exists与in及any的用法详解
MySQL 中exists与in及any的用法详解
262 3
|
JSON 前端开发 JavaScript
JavaWeb基础8——Filter,Listener,Ajax,Axios,JSON
Filter过滤器、Listener监听器、AJAX、 同步、异步优点和使用场景、Axios异步框架、JSON、js和JSON转换、案例,Axios + JSON 品牌列表查询和添加
JavaWeb基础8——Filter,Listener,Ajax,Axios,JSON
|
NoSQL Java Redis
jedis 与 redission 实现分布式锁
jedis 与 redission 实现分布式锁
259 4
|
Linux 编译器 数据处理
探索Linux命令之nm:解析二进制文件的神器
`nm`命令是Linux下分析二进制文件的工具,显示符号表中的函数、变量等信息及它们的地址和类型。它帮助理解程序结构、调试和优化,支持不同符号类型、输出选项和过滤。常用参数如`-a`显示所有符号,`-t f`列出定义的函数。在实际应用中,可以结合其他工具如`objdump`、`readelf`进行更深入的分析,并注意备份原始文件。
|
数据可视化 API 开发工具
详细解读cesi+supervisor可视化集中管理服务器节点进程
详细解读cesi+supervisor可视化集中管理服务器节点进程
256 0