zoukankan      html  css  js  c++  java
  • 多线程环境下非安全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

  • 相关阅读:
    解决在macOS下安装了python却没有pip命令的问题【经验总结】
    Mac OS下安装MongoDB以及配置方法总结【笔记】
    web上常见的攻击方式及简单的防御方法
    Destoon二开必看执行流程
    网站入侵注入的几种方法总结【笔记】
    命令检测站点是否使用CDN加速
    织梦xss通杀所有版本漏洞【学习笔记】
    让你知晓内容安全的边界:盘点2017、2018这两年的内容监管
    知物由学 | AI网络安全实战:生成对抗网络
    人工智能热门图书(深度学习、TensorFlow)免费送!
  • 原文地址:https://www.cnblogs.com/feinian/p/4853717.html
Copyright © 2011-2022 走看看