多线程环境下非安全Dictionary引起的“已添加了具有相同键的项”问题
问题:
代码是在多线程环境下,做了简单的Key是否存的判断, 测试代码如下:
public class Program { static Dictionary<string, Logger> loggreDic; static object loggerDicLocker = new object(); public static void Main() { loggreDic = new Dictionary<string, Logger>(); for (int i = 0; i < 100; i++) { ThreadPool.QueueUserWorkItem(o => { try { var logger = GetLogger("AAA"); } catch (Exception) { Console.WriteLine(string.Format("弟{0}个线程出现问题", o)); } }, i); } Console.ReadKey(); } static Logger GetLogger(string cmdId) { if (!loggreDic.ContainsKey(cmdId)) { loggreDic.Add(cmdId, LogManager.GetLogger(string.Format("ChinaPnrApi.{0}", cmdId))); } return loggreDic[cmdId]; } }
可以看到在GetLogger的地方做了判断的处理,但是在多线程的时候还是会出现在取的时候取不到的问题。可以参考下面截图 :
从错误异常很容易判断,是在Dictionary中加了重复的Key造成的.
所以总体上来看这段代码所犯的问题是不是线程安全的代码.
解决方案 :
1. 使用Locker解决
2. 使用线程安全的
下面对两种方式都做了实现:
public interface IGetLogger { Logger GetLogger(string cmdId); } public class ConcurrentDictionaryLogger : IGetLogger { ConcurrentDictionary<string, Logger> loggreDic = new ConcurrentDictionary<string, Logger>(); public Logger GetLogger(string cmdId) { if (!loggreDic.ContainsKey(cmdId)) { loggreDic.TryAdd(cmdId, LogManager.GetLogger(string.Format("ChinaPnrApi.{0}", cmdId))); } return loggreDic[cmdId]; } } public class LockerDictionaryLogger : IGetLogger { Dictionary<string, Logger> loggreDic = new Dictionary<string, Logger>(); object locker = new object(); public Logger GetLogger(string cmdId) { if (!loggreDic.ContainsKey(cmdId)) { lock (locker) { if (!loggreDic.ContainsKey(cmdId)) { loggreDic.Add(cmdId, LogManager.GetLogger(string.Format("ChinaPnrApi.{0}", cmdId))); } } } return loggreDic[cmdId]; } }
测试代码如下:
public static void Main() { IGetLogger conLogger = new ConcurrentDictionaryLogger(); IGetLogger lockerLogger = new LockerDictionaryLogger(); CodeTimer.Time("使用ConcurrentDictionary", 1000000, () => { ThreadPool.QueueUserWorkItem(o => { try { var logger = conLogger.GetLogger("AAA"); if (logger == null) { Console.WriteLine(string.Format("弟{0}个线程获取到的值是 NULL", o)); } } catch (Exception ex) { Console.WriteLine(string.Format("弟{0}个线程出现问题, {1}", o, ex.Message)); } }); }); CodeTimer.Time("使用LockDictionary", 1000000, () => { ThreadPool.QueueUserWorkItem(o => { try { var logger = conLogger.GetLogger("AAA"); if (logger == null) { Console.WriteLine(string.Format("弟{0}个线程获取到的值是 NULL", o)); } } catch (Exception ex) { Console.WriteLine(string.Format("弟{0}个线程出现问题, {1}", o, ex.Message)); } }); }); Console.WriteLine("已执行完成"); Console.ReadKey(); }
用Release模式编译之后,测试的结果:
第一次:
第二次:
第三次:
总结:
从测试结果来看,都解决了我们上述的问题,总体的时间比值来看ConcurrentDictionary稍微优于LockDictionary, 但是差别不是很大, 第一次几乎持平.
写代码还是要多注意线程安全的问题。
上面的CodeTimer用的是: http://www.cnblogs.com/JeffreyZhao/archive/2009/03/10/codetimer.html
标签:
Dictionary
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架