在多線程內使用集合,如果未對集合做任何安全處理,就非常容易出現系統崩潰或各種錯誤。最近的項目里,使用的是socket通信后再改變了某個集合,結果導致系統直接崩潰,且無任何錯誤系統彈出。
經排查,發現問題是執行某集合后,系統就會在一定時間內退出,最后發現是使用的一個字典集合出了問題。稍微思考后,就認定了是線程安全問題。因為此集合在其它幾個地方都有線程做循環讀取。
下面是我模擬的一個示例,沒有進行任何的安全處理:
1 class PRogram 2 { 3 static MyCollection mycoll; 4 static void Main(string[] args) 5 { 6 mycoll = new MyCollection(); 7 Thread readT = new Thread(new ThreadStart(ReadMethod)); 8 readT.Start(); 9 10 Thread addT = new Thread(new ThreadStart(AddMethod));11 addT.Start();12 Console.ReadLine();13 }14 public static void AddMethod()15 {16 for(int i=0;i<10;i++)17 {18 Thread.Sleep(500);19 mycoll.Add("a"+i, i);20 }21 }22 public static void ReadMethod()23 {24 while (true)25 {26 Thread.Sleep(100);27 foreach (KeyValuePair<string, int> item in mycoll.myDic)28 {29 Console.WriteLine(item.Key + "http://t" + item.Value);30 //其它處理31 Thread.Sleep(2000);32 }33 }34 }35 }36 public class MyCollection37 {38 public Dictionary<string, int> myDic = new Dictionary<string, int>();39 40 public void Add(string key, int value)41 {42 if (myDic.ContainsKey(key))43 {44 myDic[key] += 1;45 }46 else47 {48 myDic.Add(key, value);49 }50 }51 52 public void Remove(string key)53 {54 if (myDic.ContainsKey(key))55 {56 myDic.Remove(key);57 }58 }59 }
在上面的示例中,創建了一個Dictionary字典對像,程序運行時,輸出了下面的錯誤:
程序運行時,輸出了上面的錯誤,僅僅輸出了一行結果
這次測試有明顯示的錯誤提示,集合已修改;可能無法執行枚舉操作。
唉,真是一個常見的問題,在foreach的時侯又修改集合,就一定會出現問題了,因為foreach是只讀的,在進行遍歷時不可以對集合進行任何修改。
看到這里,我們會想到,如果使用for循環進行逆向獲取,也許可以解決此問題。
非??上?,字典對像沒有使用索引號獲取的辦法,下面的表格轉自(http://www.49028c.com/yang_sy/p/3678905.html)
Type | 內部結構 | 支持索引 | 內存占用 | 隨機插入的速度(毫秒) | 順序插入的速度(毫秒) | 根據鍵獲取元素的速度(毫秒) |
未排序字典 | ||||||
Dictionary<T,V> | 哈希表 | 否 | 22 | 30 | 30 | 20 |
Hashtable | 哈希表 | 否 | 38 | 50 | 50 | 30 |
ListDictionary | 鏈表 | 否 | 36 | 50000 | 50000 | 50000 |
OrderedDictionary | 哈希表 +數組 | 是 | 59 | 70 | 70 | 40 |
排序字典 | ||||||
SortedDictionary<K,V> | 紅黑樹 | 否 | 20 | 130 | 100 | 120 |
SortedList<K,V> | 2xArray | 是 | 20 | 3300 | 30 | 40 |
SortList | 2xArray | 是 | 27 | 4500 | 100 | 180 |
從時間復雜度來講,從字典中通過鍵獲取值所耗費的時間分別如下:
這可如何是好,只能改為可排序的對像?然后使用for解決?
我突然想到,是否可以在循環時縮短foreach,來解決此問題呢?
想到可以在循環時先copy一份副本,然后再進行循環操作,編寫代碼,查找copy的方法。真是無奈,沒有提供任何的copy方法。唉!看來人都是用來被逼的,先改個對象吧:
把Dictionary修改成了Hashtable對像(也沒有索引排序)。代碼如下:
1 class Program 2 { 3 static MyCollection mycoll; 4 static void Main(string[] args) 5 { 6 mycoll = new MyCollection(); 7 Thread readT = new Thread(new ThreadStart(ReadMethod)); 8 readT.Start(); 9 10 Thread addT = new Thread(new ThreadStart(AddMethod));11 addT.Start();12 Console.ReadLine();13 }14 public static void AddMethod()15 {16 for(int i=0;i<10;i++)17 {18 Thread.Sleep(500);19 mycoll.Add("a"+i, i);20 }21 }22 public static void ReadMethod()23 {24 while (true)25 {26 Thread.Sleep(100);27 foreach (DictionaryEntry item in mycoll.myDic)28 {29 Console.WriteLine(item.Key + " " + item.Value);30 //其它處理31 Thread.Sleep(2000);32 }33 }34 }35 }36 public class MyCollection37 {38 public Hashtable myDic = new Hashtable();39 40 public void Add(string key, int value)41 {42 if (myDic.ContainsKey(key))43 {44 45 myDic[key] =Convert.ToInt32(myDic[key])+ 1;46 }47 else48 {49 myDic.Add(key, value);50 }51 }52 53 public void Remove(string key)54 {55 if (myDic.ContainsKey(key))56 {57 myDic.Remove(key);58 }59 }60 }
代碼一如即往的報錯,錯誤信息一樣。使用copy法試試
1 class Program 2 { 3 static MyCollection mycoll; 4 static void Main(string[] args) 5 { 6 mycoll = new MyCollection(); 7 Thread readT = new Thread(new ThreadStart(ReadMethod)); 8 readT.Start(); 9 10 Thread addT = new Thread(new ThreadStart(AddMethod));11 addT.Start();12 Console.ReadLine();13 }14 public static void AddMethod()15 {16 for(int i=0;i<10;i++)17 {18 Thread.Sleep(500);19 mycoll.Add("a"+i, i);20 }21 }22 public static void ReadMethod()23 {24 Hashtable tempHt = null;25 while (true)26 {27 Thread.Sleep(10
新聞熱點
疑難解答