.NET 并行(多核)编程系列之七 共享数据问题和解决概述
原文:.NET 并行(多核)编程系列之七 共享数据问题和解决概述
.NET 并行(多核)编程系列之七 共享数据问题和解决概述
前言:之前的文章介绍了了并行编程的一些基础的知识,从本篇开始,将会讲述并行编程中实际遇到一些问题,接下来的几篇将会讲述数据共享问题。
本篇的议题如下:
- 数据竞争
- 解决方案提出
- 顺序的执行解决方案
- 数据不变解决方案
系列文章链接:
在开始之前,首先,我们来看一个很有趣的例子:
// wait for input before exiting
Console.WriteLine("Press enter to finish");
Console.ReadLine();
}
}
在上面的例子中,创建了10个task,每个task都是把BankAccount.Balance自增1000次。之后代码就等到10个task执行完毕,然后打印出Balance的值。大家猜想一下,上次的代码执行完成之后,打印出来的Balance的结果是多少?
J结果确实和大家猜想的一样:结果不等于10000。每次执行一次上面的代码,都会得到不同的结果,而且这些结果值都在10000左右,如果运气好,可能看到有那么一两次结果为10000.为什么会这样?
下面就是本篇和接下来的几篇文章要讲述的内容。
- 数据竞争
如果大家对多线程编程比较熟悉,就知道上面情况的产生是因为 “共享数据竞争”导致的(对多线程不熟悉不清楚的朋友也不用担心)。当有两个或者更多的task在运行并且操作同一个共享公共数据的时候,就存在潜在的竞争。如果不合理的处理竞争问题,就会出现上面意想不到的情况。
下面就来分析一下:上面代码的情况是怎么产生的。
当在把account对象的Balance进行自增的时候,一般执行下面的三个步骤:
- 读取现在account对象的Balance属性的值。
- 计算,创建一个临时的新变量,并且把Balance属性的值赋值给新的变量,而且把新变量的值增加1
- 把新变量的值再次赋给account的Balance属性
在理论上面,上面的三个步骤是代码的执行步骤,但是实际中,由于编译器,.NET 运行时对自增操作的优化操作,和操作系统等的因素,在执行上面代码的时候,并不一定是按照我们设想的那样运行的,但是为了分析的方便,我们还是假设代码是按照上面的三个步骤运行的。
之前的代码每次执行一次,执行代码的计算机就每次处于不同的状态:CPU的忙碌状况不同,内存的剩余多少不同,等等,所以每次代码的运行,计算机不可能处于完全一样的环境中。
在下面的图中,显示了两个task之间是如何发生竞争的。当两个task启动了之后(虽然说是并行运算,但是不管这样,两个的task的执行时间不可能完全一样,也就是说,不可能恰好就是同时开始执行的,起码在开始执行的时间上是有一点点的差异的)。
1. 首先Task1读取到当前的balance的值为0。
2. 然后,task2运行了,并且也读取到当前的balance值为0。
3. 两个task都把balance的值加1
4. Task1把balance的值加1后,把新的值保存到了balance中
5. Task2 也把新的保存到了balance中
所以,结果就是:虽然两个task 都为balance加1,但是balance的值还是1。
通过这个例子,相信大家应该清楚,为什么上面的10个task执行1000,而执行后的结果不是10000了。
2. 解决方案提出
数据竞争就好比一个生日party。其中,每一个task都是参加party的人,当生日蛋糕出来之后,每个人都兴奋了。如果此时,所有的人都一起冲过去拿属于他们自己的那块蛋糕,此时party就一团糟了,没有如何顺序。
在之前的图示例讲解中,balance那个属性就好比蛋糕,因为task1,task2都要得到它,然后进行运算。当我们来让多个task共享一个数据时就可能出现问题。下面列出了四种解决方案:
1. 顺序执行:也就是让第一个task执行完成之后,再执行第二个。
2. 数据不变:我们让task不能修改数据。
3. 隔离:我们不共享数据,让每个task都有一份自己的数据拷贝。
4. 同步:通过调整task的执行,有序的执行task。
注意:同步和以前多线程中的同步,或者数据库操作时的同步概念不一样
3. 顺序的执行的解决方案
顺序的执行解决了通过每次只有一个task访问共享数据的方式解决了数据竞争的问题,其实在本质上,这种解决方案又回到了之前的单线程编程模型。如果拿之前的party分蛋糕的例子,那么现在就是一次只能允许一个人去拿蛋糕。
- 数据不变解决方案
数据不变的解决方案就是通过让数据不能被修改的方式来解决共享数据竞争。如果拿之前的蛋糕为例子,那么此时的情况就是:现在蛋糕只能看,不能吃。
在C#中,可以同关键字 readonly 和 const来声明一个字段不能被修改:
public const int AccountNumber=123456;
被声明为const的字段只能通过类型来访问:如,上面的AccountNumber是在Blank类中声明的,那么访问的方式就是Blank. AccountNumber
readonly的字段可以在实例的构造函数中修改。
如下代码:
Console.WriteLine("Account Number: {0}, Account Balance: {1}",
ImmutableBankAccount.AccountNumber, bankAccount2.Balance);
// wait for input before exiting
Console.WriteLine("Press enter to finish");
Console.ReadLine();
}
}
数据不变的解决方案不是很常用,因为它对数据限制太大了。
今天暂时就写到这里,谢谢大家。
版权为小洋和博客园所有,转载请标明出处给作者。
http://www.cnblogs.com/yanyangtian
最新文章
- WCF学习之旅——第一个WCF示例(三)
- CSS之照片集效果
- PKu 2195
- TDI - Transport Driver Interface
- Dynamics AX 2012 R2 为运行失败的批处理任务设置预警
- 查看linux发行版本、内核版本命令
- Java并发编程-关卡
- Java HttpClient使用小结
- easyui源码翻译1.32--Droppable(放置)
- MVC过滤器的详细讲解和示范样本
- 最大流模版 EK
- RTF文件格式
- ionic上拉加载组件 ion-infinite-scroll自动调用多次的问题
- JasperReport子报表参数传递
- 改变下blog思维
- 【IDEA&;&;Eclipse】4、IntelliJ IDEA上操作GitHub
- [源码]underscore-1.8.3
- python apply()函数
- Cocos2d-x 3.1.1 学习日志7--7分钟让你了解cocos2d-x3.1.1 Sprite精灵类
- POJ 3276 Face The Right Way (尺取法)
热门文章
- 《数字图像处理原理与实践(MATLAB版)》一书之代码Part2
- Xenu-web开发死链接检測工具应用
- mac和windows系统下 eclipse svn 设置代理服务器
- eval、json.parse()的介绍和使用注意点
- SVN更改登录用户(转)
- ERROR: Error in Log_event::read_log_event(): 'read error', data_len: 438, event_type: 2
- POJ 1753 位运算+枚举
- 黄聪:Microsoft Enterprise Library 5.0 系列教程(十) Configuration Application Block
- hdu 2074 堆放篮 好开心图纸标题
- codeforces #256 A. Rewards