ConcurrentDictionary是.net BCL的一个线程安全的字典类,由于其方法的线程安全性,使用时无需手动加锁,被广泛应用于多线程编程中。然而,有的时候他们并不是如我们预期的那样工作。

拿它的一个GetOrAdd方法为例, 它的定义如下:

public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory);

这是一个非常常用的方法,MSDN对它的描述为: 需要检索指定键的现有值,如果此键不存在,则需要指定一个键/值对。其行为模式是:

  • 第一次调用的时候会调用valueFactory创建值并返回
  • 后续调用的线程会直接返回字典中的检索值,valueFactory不会执行。

也就是说valueFactory只会在第一次调用的时候执行。由于微软在MSDN中说明这个函数是线程安全的,我一直以为其在并发执行的时候行为也是一样的,认为valueFactory只会执行一次。并且它也运行结果也一直如我所预期,然而今天定位一个问题的时候,通过日志发现其valueFactory是会执行多次的。

为了简单的展示这个问题,我这里写了一段简单的代码。

var dic = new ConcurrentDictionary<int, int>();for (int i = 0; i < 6; i++)
{runInNewThread(i);
}void runInNewThread(int i)
{var thread = new Thread(para => dic.GetOrAdd(1, _ => getNum((int)para)));thread.Start(i);
}int getNum(int i)
{Console.WriteLine($"Factory invoke. got {i}");return i;
}

执行这段代码,结果如下:

Factory invoke. got 1
Factory invoke. got 4
Factory invoke. got 2
Factory invoke. got 0
Factory invoke. got 3
Factory invoke. got 5

也就是说,其valueFactory函数getNum是执行了6次的,并不是和我预期的结果一样的。便回头翻了下MSDN,发现MSDN在文章如何:在 ConcurrentDictionary 中添加和移除项中描述了这个现象。

简单的讲,微软设计这个函数时,将其设计成了线程安全的,但不是原子的。也就是说,微软的这个函数实现的方式是

lock (getOperation)
{get();
}lock (addOperation)
{create_add();
}

而我认为它的执行方式是,

lock (operation)
{get();create_add();
}

因此会出现我预期外的valueFactory函数执行多次的情况。微软MSDN中描述了一种更严重的情况:

  1. threadA 调用 GetOrAdd,未找到项,通过调用 valueFactory 委托创建要添加的新项。
  2. threadB 并发调用 GetOrAdd,其 valueFactory 委托受到调用,并且它在 threadA 之前到达内部锁,并将其新键值对添加到词典中。
  3. threadA 的用户委托完成,此线程到达锁位置,但现在发现已有项存在
  4. threadA 执行"Get",返回之前由 threadB 添加的数据。

因此,无法保证 GetOrAdd 返回的数据与线程的 valueFactory 创建的数据相同。 调用 AddOrUpdate 时可能发生相似的事件序列。

这个问题是非常隐蔽的,这个行为大部分的时候并不会造成问题,因为

  1. GetOrAdd同时执行的几率较小,valueFactory不会执行多遍
  2. 大部分的时候valueFactory是线程安全的,同时执行了多遍也看不出来

网上也有人讨论了这个问题:

  • Making ConcurrentDictionary GetOrAdd thread safe using Lazy
  • ConcurrentDictionary线程不安全么,你难道没疑惑,你难道弄懂了么?

对于valueFactory只允许执行一遍的场景,这两篇文章中也提到了同样的解决方法,那就是使用Lazy<Value>,相当于需要两次才执行实际的valueFactory函数。

这种方式下,第一次valueFactory虽然会执行多遍,但没有执行实际的创建操作,而在使用的时候Lazy<Value>使用的时候Lazy的原子性保证第二次valueFactory创建操作只会执行一次。

当然,也有更简单粗暴的做法,那就是对GetOrAdd和AddOrUpdate加锁,但那样的需要在所有调用的地方都加锁,实际实行起来很容易漏。

关于ConcurrentDictionary的线程安全相关推荐

  1. C# 中 ConcurrentDictionary 一定线程安全吗?

    根据 .NET 官方文档的定义:ConcurrentDictionary<TKey,TValue> Class 表示可由多个线程同时访问的线程安全的键/值对集合.这也是我们在并发任务中比较 ...

  2. 一个小技巧助您减少if语句的状态判断

    在进行项目的开发的过程中, if 语句是少不了的,但我们始终要有一颗消灭 if / else 语句的心.为了消灭if / else 我们引入了 短路器 的概念.短路器 有时候的确能精简我们的代码,但还 ...

  3. ConsurrentDictionary并发字典知多少?

    在上一篇文章你真的了解字典吗?一文中我介绍了Hash Function和字典的工作的基本原理. 有网友在文章底部评论,说我的Remove和Add方法没有考虑线程安全问题. https://docs.m ...

  4. c#集合_键值对Dictionary SortedList

    前言: 在 C# 中,键值对是一种常见的数据结构,可以使用不同的集合类实现.以下是常用的键值对集合类: Dictionary<TKey, TValue>:一种使用哈希表实现的键值对集合.它 ...

  5. 多线程编程指南 part 2

    多线程编程指南 Sun Microsystems, Inc. 4150 Network Circle Santa Clara, CA95054 U.S.A. 文件号码819–7051–10 2006 ...

  6. ConcurrentDictionary线程不安全么,你难道没疑惑,你难道弄懂了么?

    前言 事情不太多时,会时不时去看项目中同事写的代码可以作个参考或者学习,个人觉得只有这样才能走的更远,抱着一副老子天下第一的态度最终只能是井底之蛙.前两篇写到关于断点传续的文章,还有一篇还未写出,后续 ...

  7. ConcurrentDictionary:.NET 4.0中新的线程安全的哈希表

    ConcurrentDictionary 是.NET 4.0中在并行和并发编程方面显著增强的基石.但是在对其进行深入研究之前,让我们来回顾一下在.NET之前版本中存在的问题. .NET中哈希表的第一个 ...

  8. .NET的ConcurrentDictionary,线程安全集合类

    ConcurrentDictionary 是.NET 4.0里面新增的号称线程安全的集合类. 那么自然,我们会预期ConcurrentDictionary 中的代码是线程安全的(至少几个关键方法是线程 ...

  9. C#线程安全集合ConcurrentDictionary

    C#线程安全集合ConcurrentDictionary 这个在System.Collections.Concurrent的字典类跟字典Dictionary的使用差不多是一样的,但在多线程并发访问的时 ...

最新文章

  1. 【面试】Java基础中的那些事-One
  2. linux 安装 tao环境,linux环境安装hbase------不一定需要hadoop
  3. (0082)iOS开发之搭建iOS自动化打包平台(利用Jenkins持续集成iOS项目)
  4. DataGridView和DataTable同步排序
  5. SqlServer用户数据库的系统视图sysobjects、syscolumns、systypes
  6. git 添加用户名和邮箱_设置 Git 账户及邮箱
  7. xxl-job执行定时job原理
  8. .Net Core迁移到MSBuild平台
  9. python tkinter控件_Python3 tkinter基础 Label pack 设置控件在窗体中的位置
  10. SDN精华问答 | SDN的核心技术是什么?
  11. 2017.3.14 不重复数字 思考记录
  12. 从解放劳动力来看未来的科技进程
  13. HFSS15.0 安装破解说明
  14. usaco training 5.1 星空之夜
  15. 一款很哇塞的csdn开发助手,你确定不来看看嘛
  16. matlab 期权delta,欧式期权定价(BS方法delta值和隐含波动率计算)
  17. 冯·米塞斯迭代法(Von Mises iteration)
  18. 原创【歌词类】雪中吟
  19. 联想惠普谁才是pc的最后王者
  20. 网游“梦幻西游”“my.exe”在Win7(或XP)下出现“已停止工作”报错无法运行的解决方法

热门文章

  1. unity3d-ngui UIScrollView 滚动方向与滚轮相反
  2. UML建模工具Visio、Rational Rose、PowerDesign,Visual Paradigm for UML
  3. 数据库msqlserver的几种类型及解决MSSQLServer服务启动不了的问题
  4. (转载)Vim入门图解说明
  5. C++学习笔记————WINAPI宏定义
  6. c++ 获取操作的精确时间
  7. VC++工作笔记0003---C++中的explicit关键字
  8. 数据库工作笔记018---Windows下mysql安装_服务无法启动没有报告解决
  9. 隐式类型转换与转换操作符operator T
  10. 如何判断两个矩形相交