关于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 = ; i < ; i++)
{
runInNewThread(i);
} void runInNewThread(int i)
{
var thread = new Thread(para => dic.GetOrAdd(, _ => 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是线程安全的,同时执行了多遍也看不出来
网上也有人讨论了这个问题:
对于valueFactory只允许执行一遍的场景,这两篇文章中也提到了同样的解决方法,那就是使用Lazy<Value>,相当于需要两次才执行实际的valueFactory函数。
这种方式下,第一次valueFactory虽然会执行多遍,但没有执行实际的创建操作,而在使用的时候Lazy<Value>使用的时候Lazy的原子性保证第二次valueFactory创建操作只会执行一次。
当然,也有更简单粗暴的做法,那就是对GetOrAdd和AddOrUpdate加锁,但那样的需要在所有调用的地方都加锁,实际实行起来很容易漏。
关于ConcurrentDictionary的线程安全的更多相关文章
- C# 中 ConcurrentDictionary 一定线程安全吗?
根据 .NET 官方文档的定义:ConcurrentDictionary<TKey,TValue> Class 表示可由多个线程同时访问的线程安全的键/值对集合.这也是我们在并发任务中比较 ...
- .net framework 4 线程安全概述
线程安全:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的.早期的时候, ...
- ConcurrentDictionary并发字典知多少?
背景 在上一篇文章你真的了解字典吗?一文中我介绍了Hash Function和字典的工作的基本原理. 有网友在文章底部评论,说我的Remove和Add方法没有考虑线程安全问题. https://doc ...
- ConcurrentDictionary内部机制粗解
ConcurrentDictionary是线程安全类,是什么在保证? 内部类 private class Tables { internal readonly Node[] m_buckets; // ...
- ABP使用及框架解析系列 - [Unit of Work part.2-框架实现]
前言 ABP ABP是“ASP.NET Boilerplate Project”的简称. ABP的官方网站:http://www.aspnetboilerplate.com ABP在Github上的开 ...
- [Asp.net 5] Localization-简单易用的本地化-全球化信息
本篇比较简单介绍Localization解决方案中: Microsoft.Framework.Globalization.CultureInfoCache 工程 CultureInfoGenerato ...
- C#学习笔记(一):一些零散但重要的知识点汇总
集合类型 数组 数组需要注意的就是多维数组和数组的数组之间的区别,如下: using System; namespace Study { class Program { static void Mai ...
- Unit of Work
ABP使用及框架解析系列 - [Unit of Work part.2-框架实现] 前言 ABP ABP是“ASP.NET Boilerplate Project”的简称. ABP的官方网站:ht ...
- C#集合类型大揭秘 【转载】
[地址]https://www.cnblogs.com/songwenjie/p/9185790.html 集合是.NET FCL(Framework Class Library)的重要组成部分,我们 ...
随机推荐
- [机器学习]SVM---硬间隔最大化数学原理
注:以下的默认为2分类 1.SVM原理: (1)输入空间到特征空间得映射 所谓输入空间即是输入样本集合,有部分情况输入空间与特征空间是相同得,有一部分情况二者是不同的,而模型定义都是定义到特征空间的, ...
- 【NOI】2017 整数(BZOJ 4942,LOJ2302) 压位+线段树
[题目]#2302. 「NOI2017」整数 [题意]有一个整数x,一开始为0.n次操作,加上a*2^b,或询问2^k位是0或1.\(n \leq 10^6,|a| \leq 10^9,0 \leq ...
- 20155233 2016-2017-2 《Java程序设计》第5周学习总结
20155233 2016-2017-2 <Java程序设计>第5周学习总结 学习目标 理解异常架构 牚握try...catch...finally处理异常的方法 会用throw,thro ...
- HDU 2544 最短路 最短路问题
解题报告: 这题就是求两个单源点之间的最小距离,属于最短路问题,由于数据量很小,只有100,所以这题可以用弗洛伊德也可以用迪杰斯特拉,都可以过,但是用迪杰斯特拉会快一点,但用弗洛伊德的代码会稍短一点, ...
- django+mysql安装和设置
之前我们已经用sqlite建立了第一个web app.今天来学习如何在django中使用MySQL. 首先需要安装MySQL,到官网下载安装包:https://dev.mysql.com/downlo ...
- vue实战之狗血事件:页面loading效果诡异之事
接上回 想加一个切换路由时,跳出一个loading动画 ,路由加载后就消失 先做了一个loading提示的浮动层的组件,全局注册,在几个路由页面都引入 在vuex里面维护一个变量比如isLoading ...
- [uart]1.Linux中tty框架与uart框架之间的调用关系剖析
转自:http://developer.51cto.com/art/201209/357501_all.htm 目录 1.tty框架 2.uart框架 3.自底向上 4.自顶向下 5.关系图 在这期间 ...
- pip 18.1: pipenv graph results in ImportError: cannot import name 'get_installed_distributions'
I'm currently using python3 -m pip install pip==10.0.1python3 -m pip install pipenv==2018.5.18 Once ...
- Android BLE设备蓝牙通信框架BluetoothKit
BluetoothKit是一款功能强大的Android蓝牙通信框架,支持低功耗蓝牙设备的连接通信.蓝牙广播扫描及Beacon解析. 关于该项目的详细文档请关注:https://github.com/d ...
- oracle数据库_实例_用户_表空间之间的关系
基础概念:Oracle数据库.实例.用户.表空间.表之间的关系 数据库:Oracle数据库是数据的物理存储.这就包括(数据文件ORA或者DBF.控制文件.联机日志.参数文件).其实Oracle数据库的 ...