抓虫系列(一) 从简单程序开始 线程安全
简单的程序也可以存在很多值得思考的地方,作为一名程序员或者架构师,首先要具备的就是追根和追新的心态。抓虫系列的代码我想大部分人都接触过或者犯过这样的错误,有些可能涉及的知识面很基础很浅,留个烂文在此引导新手、路人。虫子尽量将问题放大,追的深一点偏一点,如果大家有其他自己的想法或者补充也可以留爪印。
先看原始bug程序
1 class testObj
2 {
3 public object Result { get; set; }
4 public int index { get; set; }
5 }
01 public void Test()
02 {
03 ManualResetEvent[] MR = new ManualResetEvent[20];
04
05 testObj qq = new testObj();
06
07 for (int i = 0; i < 20; i++)
08 {
09 MR[i] = new ManualResetEvent(false);
10 qq.index = i;
11 ThreadPool.QueueUserWorkItem(o =>
12 {
13 Console.WriteLine(qq.index.ToString());
14 MR[qq.index].Set();
15
16 }, qq);
17 }
18 WaitHandle.WaitAll(MR);
19 }
我们的目的是让程序输出0~19。看到这里可能老鸟已经发现程序的问题了。新鸟应该还是继续查虫。老鸟们先卖个关子,虫子把问题引偏,这样好拓展更多的问题。让我们来看看运行结果。
当时我就震惊了... 虫子开始胡思乱想了 为嘛会是这样。堆栈问题?好吧巩固一下堆栈,值类型是放在堆中的,值类型的拷贝是深拷贝,当我们传递一个值类型参数时,栈上被分配好一个新的空间,然后该参数的值被拷贝到此空间中。应该不会产生这样的现象吧。 不对!!!int已经被封装在testObj里了,这个地方它是引用类型是浅拷贝。Oh,MyGirlFriend,发现了!这20个线程用的是同一个副本,(⊙o⊙)… 虫子在干什么,你让20个线程在同时操作同一个数据。
这是个问题,。上面所说是一个问题但是不是这个现象产生的唯一问题。在这个异步程序中,线程在获取对象资源时,那for循环再主线程中已经跑完了。所以qq的index一直是20。读到这里可能有些老鸟们已经开始嗤之以鼻,这种错误他们可不会犯。知道了原因我们就要来分析解决方案了。一层一层来剥,
首先看这段修补01号程序
01 public void Test()
02 {
03 ManualResetEvent[] MR = new ManualResetEvent[20];
04 EventWaitHandle EH = new AutoResetEvent(false);
05 testObj qq = new testObj();
06 EH.Set();
07 for (int i = 0; i < 20; i++)
08 {
09 MR[i] = new ManualResetEvent(false);
10 qq.index = i;
11 EH.WaitOne();
12 ThreadPool.QueueUserWorkItem(o =>
13 {
14 Console.WriteLine(qq.index.ToString());
15 MR[qq.index].Set();
16 EH.Set();
17 }, qq);
18 }
19 WaitHandle.WaitAll(MR);
20 }
我们加了AutoResetEvent,这是一个自动Reset的事件通知方式。形象点说,相当于各位经常使用的门禁系统。一开始处于wait状态,只有有人set了它才放行。这里用来控制线程一个一个来完成,也就是说每次对象只有一个人能访问。这下咱们的线程安全了吧,哈哈哈哈。再看效果图。
神马!!! 貌似看好多了,居然还有重复的看那1、1,19、19这是多么的不和谐啊。好吧,继续抓虫、(⊙o⊙)…又发现了 主线程虽然被门禁控制住了,但是主线程和异步线程的节奏还是不一样,运气好点可能你出的结果是正确的。但是主线程在第一次的时候可能已经pass掉了 但是第一个异步线程还没结束。(⊙o⊙)… 虫子你骗我们 这个方案根本不是解决这个问题的,我们的问题在于我们的程序用了同一个资源qq。
被你发现了!!!
好吧修补02号程序如下
01 public void Test()
02 {
03 ManualResetEvent[] MR = new ManualResetEvent[20];
04 &n
补充:综合编程 , 安全编程 ,