关于ConcurrentDictionary的线程安全
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中描述了一种更严重的情况:
- threadA 调用 GetOrAdd,未找到项,通过调用 valueFactory 委托创建要添加的新项。
- threadB 并发调用 GetOrAdd,其 valueFactory 委托受到调用,并且它在 threadA 之前到达内部锁,并将其新键值对添加到词典中。
- threadA 的用户委托完成,此线程到达锁位置,但现在发现已有项存在
- threadA 执行"Get",返回之前由 threadB 添加的数据。
因此,无法保证 GetOrAdd 返回的数据与线程的 valueFactory 创建的数据相同。 调用 AddOrUpdate 时可能发生相似的事件序列。
这个问题是非常隐蔽的,这个行为大部分的时候并不会造成问题,因为
- GetOrAdd同时执行的几率较小,valueFactory不会执行多遍
- 大部分的时候valueFactory是线程安全的,同时执行了多遍也看不出来
网上也有人讨论了这个问题:
- Making ConcurrentDictionary GetOrAdd thread safe using Lazy
- ConcurrentDictionary线程不安全么,你难道没疑惑,你难道弄懂了么?
对于valueFactory只允许执行一遍的场景,这两篇文章中也提到了同样的解决方法,那就是使用Lazy<Value>,相当于需要两次才执行实际的valueFactory函数。
这种方式下,第一次valueFactory虽然会执行多遍,但没有执行实际的创建操作,而在使用的时候Lazy<Value>使用的时候Lazy的原子性保证第二次valueFactory创建操作只会执行一次。
当然,也有更简单粗暴的做法,那就是对GetOrAdd和AddOrUpdate加锁,但那样的需要在所有调用的地方都加锁,实际实行起来很容易漏。
关于ConcurrentDictionary的线程安全相关推荐
- C# 中 ConcurrentDictionary 一定线程安全吗?
根据 .NET 官方文档的定义:ConcurrentDictionary<TKey,TValue> Class 表示可由多个线程同时访问的线程安全的键/值对集合.这也是我们在并发任务中比较 ...
- 一个小技巧助您减少if语句的状态判断
在进行项目的开发的过程中, if 语句是少不了的,但我们始终要有一颗消灭 if / else 语句的心.为了消灭if / else 我们引入了 短路器 的概念.短路器 有时候的确能精简我们的代码,但还 ...
- ConsurrentDictionary并发字典知多少?
在上一篇文章你真的了解字典吗?一文中我介绍了Hash Function和字典的工作的基本原理. 有网友在文章底部评论,说我的Remove和Add方法没有考虑线程安全问题. https://docs.m ...
- c#集合_键值对Dictionary SortedList
前言: 在 C# 中,键值对是一种常见的数据结构,可以使用不同的集合类实现.以下是常用的键值对集合类: Dictionary<TKey, TValue>:一种使用哈希表实现的键值对集合.它 ...
- 多线程编程指南 part 2
多线程编程指南 Sun Microsystems, Inc. 4150 Network Circle Santa Clara, CA95054 U.S.A. 文件号码819–7051–10 2006 ...
- ConcurrentDictionary线程不安全么,你难道没疑惑,你难道弄懂了么?
前言 事情不太多时,会时不时去看项目中同事写的代码可以作个参考或者学习,个人觉得只有这样才能走的更远,抱着一副老子天下第一的态度最终只能是井底之蛙.前两篇写到关于断点传续的文章,还有一篇还未写出,后续 ...
- ConcurrentDictionary:.NET 4.0中新的线程安全的哈希表
ConcurrentDictionary 是.NET 4.0中在并行和并发编程方面显著增强的基石.但是在对其进行深入研究之前,让我们来回顾一下在.NET之前版本中存在的问题. .NET中哈希表的第一个 ...
- .NET的ConcurrentDictionary,线程安全集合类
ConcurrentDictionary 是.NET 4.0里面新增的号称线程安全的集合类. 那么自然,我们会预期ConcurrentDictionary 中的代码是线程安全的(至少几个关键方法是线程 ...
- C#线程安全集合ConcurrentDictionary
C#线程安全集合ConcurrentDictionary 这个在System.Collections.Concurrent的字典类跟字典Dictionary的使用差不多是一样的,但在多线程并发访问的时 ...
最新文章
- 【面试】Java基础中的那些事-One
- linux 安装 tao环境,linux环境安装hbase------不一定需要hadoop
- (0082)iOS开发之搭建iOS自动化打包平台(利用Jenkins持续集成iOS项目)
- DataGridView和DataTable同步排序
- SqlServer用户数据库的系统视图sysobjects、syscolumns、systypes
- git 添加用户名和邮箱_设置 Git 账户及邮箱
- xxl-job执行定时job原理
- .Net Core迁移到MSBuild平台
- python tkinter控件_Python3 tkinter基础 Label pack 设置控件在窗体中的位置
- SDN精华问答 | SDN的核心技术是什么?
- 2017.3.14 不重复数字 思考记录
- 从解放劳动力来看未来的科技进程
- HFSS15.0 安装破解说明
- usaco training 5.1 星空之夜
- 一款很哇塞的csdn开发助手,你确定不来看看嘛
- matlab 期权delta,欧式期权定价(BS方法delta值和隐含波动率计算)
- 冯·米塞斯迭代法(Von Mises iteration)
- 原创【歌词类】雪中吟
- 联想惠普谁才是pc的最后王者
- 网游“梦幻西游”“my.exe”在Win7(或XP)下出现“已停止工作”报错无法运行的解决方法
热门文章
- unity3d-ngui UIScrollView 滚动方向与滚轮相反
- UML建模工具Visio、Rational Rose、PowerDesign,Visual Paradigm for UML
- 数据库msqlserver的几种类型及解决MSSQLServer服务启动不了的问题
- (转载)Vim入门图解说明
- C++学习笔记————WINAPI宏定义
- c++ 获取操作的精确时间
- VC++工作笔记0003---C++中的explicit关键字
- 数据库工作笔记018---Windows下mysql安装_服务无法启动没有报告解决
- 隐式类型转换与转换操作符operator T
- 如何判断两个矩形相交