多线程环境下非安全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的地方做了判断的处理,但是在多线程的时候还是会出现在取的时候取不到的问题。可以参考下面截图 :

image

从错误异常很容易判断,是在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模式编译之后,测试的结果:

第一次:

image

第二次:

image

第三次:

image

总结:

从测试结果来看,都解决了我们上述的问题,总体的时间比值来看ConcurrentDictionary稍微优于LockDictionary, 但是差别不是很大, 第一次几乎持平.

写代码还是要多注意线程安全的问题。

 

上面的CodeTimer用的是: http://www.cnblogs.com/JeffreyZhao/archive/2009/03/10/codetimer.html

posted @   DukeCheng  阅读(903)  评论(0编辑  收藏  举报
编辑推荐:
· 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 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示