博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
抓虫系列(一) 从简单程序开始 线程安全
阅读量:5749 次
发布时间:2019-06-18

本文共 3025 字,大约阅读时间需要 10 分钟。

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

PS一下:看了下面的评论,大家有点误会虫子的意思了,此系列的博文旨在抓虫,从抓虫中关注我们的程序中容易出现的问题而并非是找寻更佳的解决方案 嘿嘿~ ~

先看原始bug程序

1
2
3
4
5
class 
testObj
    
{    
        
public 
object 
Result { 
get
set
; }
        
public 
int 
index { 
get
set
; }      
    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public 
void 
Test()
       
{
           
ManualResetEvent[] MR = 
new 
ManualResetEvent[20];
 
           
testObj qq = 
new 
testObj();
 
           
for 
(
int 
i = 0; i < 20; i++)
           
{
               
MR[i] = 
new 
ManualResetEvent(
false
);
               
qq.index = i;
               
ThreadPool.QueueUserWorkItem(o =>
               
{
                   
Console.WriteLine(qq.index.ToString());
                   
MR[qq.index].Set();
 
               
}, qq);
           
}
           
WaitHandle.WaitAll(MR);
       
}

我们的目的是让程序输出0~19。看到这里可能老鸟已经发现程序的问题了。新鸟应该还是继续查虫。老鸟们先卖个关子,虫子把问题引偏,这样好拓展更多的问题。让我们来看看运行结果。

当时我就震惊了... 虫子开始胡思乱想了 为嘛会是这样。堆栈问题?好吧巩固一下堆栈,值类型是放在栈中的,值类型的拷贝是深拷贝,当我们传递一个值类型参数时,栈上被分配好一个新的空间,然后该参数的值被拷贝到此空间中。应该不会产生这样的现象吧。  不对!!! int已经被封装在testObj里了,这个地方它是引用类型是浅拷贝。Oh,MyGirlFriend,发现了!这20个线程用的是同一个副本,(⊙o⊙)… 虫子在干什么,你让20个线程在同时操作同一个数据。

这是个问题,。上面所说是一个问题但是不是这个现象产生的唯一问题。在这个异步程序中,线程在获取对象资源时,那for循环再主线程中已经跑完了。所以qq的index一直是20。读到这里可能有些老鸟们已经开始嗤之以鼻,这种错误他们可不会犯。知道了原因我们就要来分析解决方案了。一层一层来剥,

首先看这段修补01号程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public 
void 
Test()
        
{
            
ManualResetEvent[] MR = 
new 
ManualResetEvent[20];
            
EventWaitHandle EH = 
new 
AutoResetEvent(
false
);
            
testObj qq = 
new 
testObj();
            
EH.Set();
            
for 
(
int 
i = 0; i < 20; i++)
            
{
                
MR[i] = 
new 
ManualResetEvent(
false
);
                
qq.index = i;
                
EH.WaitOne();
                
ThreadPool.QueueUserWorkItem(o =>
                
{
                    
Console.WriteLine(qq.index.ToString());
                    
MR[qq.index].Set();
                    
EH.Set();
                
}, qq);
            
}
            
WaitHandle.WaitAll(MR);
        
}

我们加了AutoResetEvent,这是一个自动Reset的事件通知方式。形象点说,相当于各位经常使用的门禁系统。一开始处于wait状态,只有有人set了它才放行。这里用来控制线程一个一个来完成,也就是说每次对象只有一个人能访问。这下咱们的线程安全了吧,哈哈哈哈。再看效果图。

神马!!! 貌似看好多了,居然还有重复的看那1、1,19、19这是多么的不和谐啊。好吧,继续抓虫、(⊙o⊙)…又发现了 主线程虽然被门禁控制住了,但是主线程和异步线程的节奏还是不一样,运气好点可能你出的结果是正确的。但是主线程在第一次的时候可能已经pass掉了 但是第一个异步线程还没结束。(⊙o⊙)… 虫子你骗我们 这个方案根本不是解决这个问题的,我们的问题在于我们的程序用了同一个资源qq。

被你发现了!!!

好吧修补02号程序如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public 
void 
Test()
        
{
            
ManualResetEvent[] MR = 
new 
ManualResetEvent[20];
            
testObj qq = 
new 
testObj();
            
for 
(
int 
i = 0; i < 20; i++)
            
{
                
qq = 
new 
testObj();
                
MR[i] = 
new 
ManualResetEvent(
false
);
                
qq.index = i;
                
ThreadPool.QueueUserWorkItem(o =>
                
{
                    
Console.WriteLine(qq.index.ToString());
                    
MR[qq.index].Set();
                
}, qq);
            
}
            
WaitHandle.WaitAll(MR);       
        
}

长叹一口气,这下没问题了吧。嘿嘿,这次我可以为每个线程独立分配了一个对象。事实如此吗?老鸟们应该开始偷着笑了,看效果图

╮(╯▽╰)╭ 你又被忽悠了,这个问题关键的地方在第四行,貌似你每个形成用的都是独立的资源,其实你还是粗心了点。

下面上正解方案了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public 
class 
cnBlog
   
{
       
public 
void 
Test()
       
{
           
ManualResetEvent[] MR = 
new 
ManualResetEvent[20];
           
for 
(
int 
i = 0; i < 20; i++)
           
{
               
testObj qq = 
new 
testObj();
               
MR[i] = 
new 
ManualResetEvent(
false
);
               
qq.index = i;
               
ThreadPool.QueueUserWorkItem(o =>
               
{
                   
Console.WriteLine(qq.index.ToString());
                   
MR[qq.index].Set();
               
}, qq);
           
}
           
WaitHandle.WaitAll(MR);       
       
}
   
}

和之前的方案只是将一行程序换了个地方,但是意义完全改变了,让我们看看真正的结果是如何。

oh yeah~ ~ 虽然排序不理想 正常!

题后话就不多说了,希望抓虫能给大家带来一些微不足道的收获。

本文转自 熬夜的虫子  51CTO博客,原文链接:http://blog.51cto.com/dubing/712447

转载地址:http://ooezx.baihongyu.com/

你可能感兴趣的文章
SAP HANA存储过程结果视图调用
查看>>
设计模式 ( 十八 ):State状态模式 -- 行为型
查看>>
OracleLinux安装说明
查看>>
nova分析(7)—— nova-scheduler
查看>>
Entity Framework 实体框架的形成之旅--Code First模式中使用 Fluent API 配置(6)
查看>>
OpenMediaVault 搭建git,ssh无法连接问题
查看>>
java多线程之:Java中的ReentrantLock和synchronized两种锁定机制的对比 (转载)
查看>>
【Web动画】SVG 实现复杂线条动画
查看>>
使用Wireshark捕捉USB通信数据
查看>>
《树莓派渗透测试实战》——1.1 购买树莓派
查看>>
Apache Storm 官方文档 —— FAQ
查看>>
iOS 高性能异构滚动视图构建方案 —— LazyScrollView
查看>>
Java 重载、重写、构造函数详解
查看>>
【Best Practice】基于阿里云数加·StreamCompute快速构建网站日志实时分析大屏
查看>>
【云栖大会】探索商业升级之路
查看>>
HybridDB实例新购指南
查看>>
C语言及程序设计提高例程-35 使用指针操作二维数组
查看>>
华大基因BGI Online的云计算实践
查看>>
深入理解自定义Annotation,实现ButterKnif小原理
查看>>
排序高级之交换排序_冒泡排序
查看>>