多线程下的集合安全

8/3/2015来源:C#应用人气:1573

多线程下的集合安全

2014-09-18 10:32 by 迟来的春天, ... 阅读, ... 评论, 收藏, 编辑

在多线程内使用集合,如果未对集合做任何安全处理,就非常容易出现系统崩溃或各种错误。最近的项目里,使用的是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.cnblogs.com/yang_sy/p/3678905.html)

Type内部结构支持索引内存占用随机插入的速度(毫秒)顺序插入的速度(毫秒)根据键获取元素的速度(毫秒)
未排序字典
Dictionary<T,V>哈希表22303020
Hashtable哈希表38505030
ListDictionary链表36500005000050000
OrderedDictionary哈希表 +数组59707040
排序字典
SortedDictionary<K,V>红黑树20130100120
SortedList<K,V>2xArray2033003040
SortList2xArray274500100180

从时间复杂度来讲,从字典中通过键获取值所耗费的时间分别如下:

  • Hashtable, Dictionary和OrderedDictionary的时间复杂度为O(1)
  • SortedDictionary和SortList的时间复杂度为O(logN)
  • ListDictinary的时间复杂度为O(n)

这可如何是好,只能改为可排序的对像?然后使用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