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