关于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)的重要组成部分,我们 ...
随机推荐
- pt-table-checksum 3.0.4检测不出主从差异数据
群里好几位同学问 pt-table-checksum 3.0.4, 主从两个表数据是不一致,为啥检测不出来?前段时间自己也测试过,只是没整理成随笔^_- 一.基本环境 VMware10.0+CentO ...
- Java内存模型-锁的内存语义
一 引言 在说volatile的内存语义时,讲过这样一句话:想要理解透volatile特性有一个很好的方法,就是把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步.所 ...
- 分模块开发创建service子模块——(八)
1.右击父工程新建maven子模块
- jdk1.8源码Thread与Runnable区别
一.概念 Thread实现了Runnable接口 public class Thread implements Runnable { /* Make sure registerNatives is t ...
- RESTful 个人理解总结【转】
转自:http://www.cnblogs.com/wang-yaz/p/9237981.html 一.什么是RESTful 面向资源 简单的说:RESTful是一种架构的规范与约束.原则,符合这种规 ...
- Shiro:授权控制
对容易忽略的地方记录如下: 1.需要引入下面2个依赖,具体版本根据自身环境修改: <dependency> <groupId>org.apache.geronimo.bundl ...
- 洛谷P3379倍增LCA
传送门 #include <iostream> #include <cstdio> #include <cstring> #include <cmath> ...
- (三)发布Dubbo服务
我们现在来学习下发布Dubbo服务,主要参考dubbo开发包里的demo源码:由浅入深的讲解下这个小demo: github地址:https://github.com/apache/incubator ...
- 体会 git 之优越性
既生瑜,何生亮.已有subversion,何需git?先有firefox叱咤一时,何需chrome来搅局? 原本以为之前的解决方案已经能够满足现时的需求,但这是真正的事实吗?直到新颖的工具降临,才惊叹 ...
- .NetCore 分页控件实现原理处理以及条件分页处理
说明 自定义一个类继承TagHelper,注意自定义类的 必须以TagHelper结尾,这个有点类是属性 Attribute的写法 protected TagHelper(); // // 摘要: / ...