在多线程内使用集合,如果未对集合做任何安全处理,就非常容易出现系统崩溃或各种错误。最近的项目里,使用的是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 + "//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.VEVb.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
新闻热点
疑难解答