当前位置:编程学习 > 网站相关 >>

抓虫系列(一) 从简单程序开始 线程安全

 

简单的程序也可以存在很多值得思考的地方,作为一名程序员或者架构师,首先要具备的就是追根和追新的心态。抓虫系列的代码我想大部分人都接触过或者犯过这样的错误,有些可能涉及的知识面很基础很浅,留个烂文在此引导新手、路人。虫子尽量将问题放大,追的深一点偏一点,如果大家有其他自己的想法或者补充也可以留爪印。

 

先看原始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

补充:综合编程 , 安全编程 ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,