首页 > 学院 > 开发设计 > 正文

多线程环境下非安全Dictionary引起的“已添加了具有相同键的项”问题

2019-11-14 13:56:41
字体:
来源:转载
供稿:网友

问题:

代码是在多线程环境下,做了简单的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.VEVb.com/JeffreyZhao/archive/2009/03/10/codetimer.html


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表