多執行緒環境下非安全Dictionary引起的“已新增了具有相同鍵的項”問題

DukeCheng發表於2015-10-03

問題:

程式碼是在多執行緒環境下,做了簡單的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

相關文章