当前位置: 首页 > news >正文

C#中ConcurrentDictionary的AddOrUpdate/GetOrAdd的并发问题

     这个是偶然发现的,我们看下面的代码:

    public static void Main(){int addCount = 0;int updateCount = 0;var dict = new ConcurrentDictionary<int, int>();for (var i = 0; i < 10; i++){new Thread(() =>{dict.AddOrUpdate(1, _ => Interlocked.Increment(ref addCount), (_, _) => Interlocked.Increment(ref updateCount));}).Start();}Console.WriteLine($"addCount={addCount}");Console.WriteLine($"updateCount={updateCount}");}

  你是否会觉得输出addCount=1,updateCount=9?就是addValueFactory只会被调用一次?调用了addValueFactory就不会调用updateValueFactory?

  再看下面的代码:

    public static void Main(){int addCount = 0;var dict = new ConcurrentDictionary<int, int>();for (var i = 0; i < 10; i++){new Thread(() =>{dict.GetOrAdd(1, _ => Interlocked.Increment(ref addCount));}).Start();}Console.WriteLine($"addCount={addCount}");}

  你是否会觉得输出addCount=1?就是valueFactory只会被调用一次?

  如果你这么觉得,那你就错了,同样的,我也被骗了这么多年!!!结果是不确定的!!

  微软只说ConcurrentDictionary是线程安全的,却没说它体现在哪里,我们看看AddOrUpdateGetOrAdd的源码就知道了

AddOrUpdate
    public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory){if (key is null){ThrowHelper.ThrowKeyNullException();}if (addValueFactory is null){ThrowHelper.ThrowArgumentNullException(nameof(addValueFactory));}if (updateValueFactory is null){ThrowHelper.ThrowArgumentNullException(nameof(updateValueFactory));}IEqualityComparer<TKey>? comparer = _comparer;int hashcode = comparer is null ? key.GetHashCode() : comparer.GetHashCode(key);while (true){if (TryGetValueInternal(key, hashcode, out TValue? oldValue)){// key exists, try to updateTValue newValue = updateValueFactory(key, oldValue);if (TryUpdateInternal(key, hashcode, newValue, oldValue)){return newValue;}}else{// key doesn't exist, try to addif (TryAddInternal(key, hashcode, addValueFactory(key), updateIfExists: false, acquireLock: true, out TValue resultingValue)){return resultingValue;}}}}
GetOrAdd
    public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory){if (key is null){ThrowHelper.ThrowKeyNullException();}if (valueFactory is null){ThrowHelper.ThrowArgumentNullException(nameof(valueFactory));}IEqualityComparer<TKey>? comparer = _comparer;int hashcode = comparer is null ? key.GetHashCode() : comparer.GetHashCode(key);if (!TryGetValueInternal(key, hashcode, out TValue? resultingValue)){TryAddInternal(key, hashcode, valueFactory(key), updateIfExists: false, acquireLock: true, out resultingValue);}return resultingValue;}

  它们内部都没有上锁,AddOrUpdate就是在一个while(true)循环内,不停的尝试修改和添加,每次循环或者调用addValueFactory或者updateValueFactoryGetOrAdd获取不到值就会去调用valueFactory,所以,如果你在这些factory里面有并发操作的话,一定要注意!!

  那怎么解决这个问题呢,你说在每个地方上锁吧,好像很繁琐,我的解决办法是使用拓展方法来解决:

DictionaryExtensions
    public static class DictionaryExtensions{public static TValue AddOrUpdateValue<TKey, TValue, TArg>(this ConcurrentDictionary<TKey, TValue> dictionary,TKey key, Func<TKey, TArg, TValue> addValueFactory, Func<TKey, TValue, TArg, TValue> updateValueFactory, TArg factoryArgument){bool hasAddValue = false;var addValue = default(TValue);bool hasUpdateValue = false;var updateValue = default(TValue);Func<TKey, TArg, TValue> newAddValueFuctory = (key, arg) =>{if (!hasAddValue){addValue = hasUpdateValue ? updateValue : addValueFactory(key, arg);hasAddValue = true;}return addValue;};Func<TKey, TValue, TArg, TValue> newUpdateValueFactory = (key, value, arg) =>{if (!hasUpdateValue){addValue = hasAddValue ? addValue : updateValueFactory(key, value, arg);hasAddValue = true;}return updateValue;};lock (dictionary){return dictionary.AddOrUpdate(key, newAddValueFuctory, newUpdateValueFactory, factoryArgument);}}public static TValue AddOrUpdateValue<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory){bool hasAddValue = false;var addValue = default(TValue);bool hasUpdateValue = false;var updateValue = default(TValue);Func<TKey, TValue> newAddValueFuctory = key =>{if (!hasAddValue){addValue = hasUpdateValue ? updateValue : addValueFactory(key);hasAddValue = true;}return addValue;};Func<TKey, TValue, TValue> newUpdateValueFactory = (key, value) =>{if (!hasUpdateValue){addValue = hasAddValue ? addValue : updateValueFactory(key, value);hasUpdateValue = true;}return updateValue;};lock (dictionary){return dictionary.AddOrUpdate(key, newAddValueFuctory, newUpdateValueFactory);}}public static TValue AddOrUpdateValue<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary, TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory){bool hasUpdateValue = false;var updateValue = default(TValue);Func<TKey, TValue, TValue> newUpdateValueFactory = (key, value) =>{if (!hasUpdateValue){addValue = updateValueFactory(key, value);hasUpdateValue = true;}return updateValue;};lock (dictionary){return dictionary.AddOrUpdate(key, addValue, newUpdateValueFactory);}}public static TValue GetOrAddValue<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TValue> valueFactory){bool hasAddValue = false;var addValue = default(TValue);Func<TKey, TValue> newAddValueFuctory = key =>{if (!hasAddValue){addValue = valueFactory(key);hasAddValue = true;}return addValue;};lock (dictionary){return dictionary.GetOrAdd(key, newAddValueFuctory);}}public static TValue GetOrAddValue<TKey, TValue, TArg>(this ConcurrentDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TArg, TValue> valueFactory, TArg factoryArgument){bool hasAddValue = false;var addValue = default(TValue);Func<TKey, TArg, TValue> newAddValueFuctory = (key, arg) =>{if (!hasAddValue){addValue = valueFactory(key, arg);hasAddValue = true;}return addValue;};lock (dictionary){return dictionary.GetOrAdd(key, newAddValueFuctory, factoryArgument);}}}

  这个是简单的封装,满足我们之前一如既往的认识,向上面的例子,我们把AddOrUpdateGetOrAdd分表改成AddOrUpdateValueGetOrAddValue就可以了。

 

http://www.jsqmd.com/news/390182/

相关文章:

  • 智慧农业中的提示系统:提示工程架构师如何处理用户反馈?
  • 题解:洛谷 P4913 【深基16.例3】二叉树深度
  • 好累
  • 2026年成都市成华区口腔医院推荐:本地人公认的“排名前五”榜单! - 资讯焦点
  • AI“智力黑洞”:让大模型变聪明的实用避坑指南
  • F. Parabola Independence
  • 小白程序员必看:大模型行业应用入门与未来机遇
  • 2026年AI大模型的发展如何影响我们的生活、工作效率及未来的职业发展
  • 详细介绍:JVM篇5:编译和解释的区分 + 区分堆栈的好处 + 垃圾回收期的选择
  • 除甲醛哪个牌子好 2026 三款长效除醛产品专业测评与场景化方案 - 资讯焦点
  • 大模型时代:数据标注从“流水线”到“专家岗”,小白也能收藏的进阶指南!
  • 小白/程序员入门大模型:阿里Qwen3.5系列详解与学习清单
  • AI去中心化系统设计:如何实现跨链互操作性?
  • 大模型与AI:从历史到应用,小白也能看懂的未来革命(收藏版)
  • 掌握LLM应用工程:从Prompt到MCP,小白也能轻松入门并收藏这趟学习之旅!
  • 2026春晚大揭秘:AI大模型重塑舞台,小白程序员必看收藏版!
  • Linux 完全卸载 MySQL
  • 语言模型推理能力的跨文化适应性评估研究
  • 大模型幻觉:定义、成因与项目开发中的规避方法
  • AI大模型真的能把很多人的工作替代掉吗?大模型AI如何改变工作?提升技能的必备指南
  • 掌握分块策略:提升RAG应用准确性的关键步骤(收藏版)
  • 题解:洛谷 P2058 [NOIP 2016 普及组] 海港
  • Shell printf 命令
  • 题解:洛谷 P1996 约瑟夫问题
  • Mac mini 带回老家,打算用远程控制,第一次开机我傻眼了
  • 2026硅酸钾领域佼佼者:盘点几家实力企业,硅微粉/石英粉/铸石粉/石英砂/石墨粉/玻璃纤维布,硅酸钾生产厂家哪家好 - 品牌推荐师
  • AI率失真:为什么你永远测不出一段文字是不是AI写的
  • 2026年专业的保健品品牌选哪家?看这篇就懂,保健品/保健饮品/养胃颗粒,保健品品牌选哪家 - 品牌推荐师
  • 2026市面上热门不锈钢筛网公司,哪个更胜一筹?混合机/旋振筛/真空上料机/不锈钢筛网,不锈钢筛网实力厂家排行榜 - 品牌推荐师
  • 2026靠谱的郭氏正骨在哪?排行榜为你揭秘,郭氏正骨,郭氏正骨生产厂家哪个好 - 品牌推荐师