之前的文章介绍了了并行编程的一些基础的知识,从本篇开始,将会讲述并行编程中实际遇到一些问题,接下来的几篇将会讲述数据共享问题。

  本篇的议题如下:

  1.数据竞争

  2.解决方案提出

  3.顺序的执行解决方案

  4.数据不变解决方案

  在开始之前,首先,我们来看一个很有趣的例子:

class BankAccount
{
public int Balance
{
get;
set;
}
}
class App
{
static void Main(string[] args)
{
// create the bank account instance
BankAccount account = new BankAccount();
// create an array of tasks
Task[] tasks = new Task[10];
for (int i = 0; i < 10; i++)
{
// create a new task
tasks[i] = new Task(() =>
{
// enter a loop for 1000 balance updates
for (int j = 0; j < 1000; j++)
{
// update the balance
account.Balance = account.Balance + 1;
}
});
// start the new task
tasks[i].Start();
}

// wait for all of the tasks to complete
Task.WaitAll(tasks);

// write out the counter value
Console.WriteLine("Expected value {0}, Counter value: {1}",
10000, account.Balance);

// 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.为什么会这样?

  下面就是本篇和接下来的几篇文章要讲述的内容。

  1.数据竞争

  如果大家对多线程编程比较熟悉,就知道上面情况的产生是因为 “共享数据竞争”导致的(对多线程不熟悉不清楚的朋友也不用担心)。当有两个或者更多的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分蛋糕的例子,那么现在就是一次只能允许一个人去拿蛋糕。

  4.数据不变解决方案

  数据不变的解决方案就是通过让数据不能被修改的方式来解决共享数据竞争。如果拿之前的蛋糕为例子,那么此时的情况就是:现在蛋糕只能看,不能吃。

  在C#中,可以同关键字 readonly 和 const来声明一个字段不能被修改:

  public const int AccountNumber=123456;

  被声明为const的字段只能通过类型来访问:如,上面的AccountNumber是在Blank类中声明的,那么访问的方式就是Blank. AccountNumber

  readonly的字段可以在实例的构造函数中修改。

  如下代码:

using System;

class ImmutableBankAccount
{
public const int AccountNumber = 123456;
public readonly int Balance;
public ImmutableBankAccount(int InitialBalance)
{
Balance = InitialBalance;
}
public ImmutableBankAccount()
{
Balance = 0;
}
}

class App
{
static void Main(string[] args)
{
// create a bank account with the default balance
ImmutableBankAccount bankAccount1 = new ImmutableBankAccount();
Console.WriteLine("Account Number: {0}, Account Balance: {1}",

ImmutableBankAccount.AccountNumber, bankAccount1.Balance);

// create a bank account with a starting balance
ImmutableBankAccount bankAccount2 = new ImmutableBankAccount(200);
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();
}
}

  数据不变的解决方案不是很常用,因为它对数据限制太大了。

转载于:https://www.cnblogs.com/waw/archive/2011/09/02/2163147.html

一起谈.NET技术,.NET并行(多核)编程系列之七 共享数据问题和解决概述相关推荐

  1. .NET 并行(多核)编程系列之七 共享数据问题和解决概述

    .NET 并行(多核)编程系列之七 共享数据问题和解决概述 原文:.NET 并行(多核)编程系列之七 共享数据问题和解决概述 .NET 并行(多核)编程系列之七 共享数据问题和解决概述 前言:之前的文 ...

  2. .NET 并行(多核)编程系列之六 Task基础部分完结篇

    .NET 并行(多核)编程系列之六 Task基础部分完结篇 前言:之前的文章介绍了了并行编程的一些基本的,也注重的讲述了Task的一些使用方法,本篇很短,将会结束Task的基础知识的介绍. 本篇的主要 ...

  3. 学习ASP.NET Core Razor 编程系列三——创建数据表及创建项目基本页面

    原文:学习ASP.NET Core Razor 编程系列三--创建数据表及创建项目基本页面 学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 ...

  4. 多核编程中伪共享问题(false sharing)

    什么是伪共享问题(False Sharing) 在计算机由单核发展到多核之后,多核编程也成为提高性能的利器.每个CPU都有自己的Cache,如果一个内存中的变量在每个cache都存在的话,就需要保证各 ...

  5. 敏捷开发“松结对编程”系列之七:问题集之一

    本文是"松结对编程"系列的第七篇.(之一,之二,之三,之四,之五,之六,之七,之八) 刚刚参加完MPD 2011深圳站,在演讲中间及后来媒体采访,被问到了一些问题,也给出了答案,这 ...

  6. C++并发编程线程间共享数据std::future和sd::promise

    线程间共享数据 使用互斥锁实现线程间共享数据 为了避免死锁可以考虑std::lock()或者boost::shared_mutex 要尽量保护更少的数据 同步并发操作 C++标准库提供了一些工具 可以 ...

  7. 一起谈.NET技术,.NET异步编程:IO完成端口与BeginRead

    写这个系列原本的想法是讨论一下.NET中异步编程风格的变化,特别是F#中的异步工作流以及未来的.NET 5.0中的基于任务的异步编程模型.但经过前几篇文章(为什么需要异步,传统的异步编程,使用CPS及 ...

  8. linux多核编程系列之调度亲和性,关于CPU亲和性,这篇讲得最全面

    何为CPU的亲和性 CPU的亲和性,进程要在某个给定的 CPU 上尽量长时间地运行而不被迁移到其他处理器的倾向性,进程迁移的频率小就意味着产生的负载小.亲和性一词是从affinity翻译来的,实际可以 ...

  9. .NET 4 并行(多核)编程系列之一入门介绍

    .NET 4 并行(多核)编程系列之一入门介绍 本系列文章将会对.NET 4中的并行编程技术(也称之为多核编程技术)以及应用作全面的介绍. 本篇文章的议题如下:   1. 并行编程和多线程编程的区别. ...

最新文章

  1. 非标准配置linux,剖析非标准波特率的设置和使用于Linux操作系统中
  2. Ubuntu常用APT命令参数
  3. 比较高效的表格行背景变色及选定高亮JS
  4. 开启MyBatis(二)创建工程
  5. 3.8 以符号常量/字面常量取代魔法数
  6. 详解 Flink 实时应用的确定性
  7. C++学习笔记----2.4 C++对象的内存模型
  8. 洛谷试炼场 计数问题
  9. 城市三级联动带城市数据
  10. 数据库 | MitoPhen 数据库:基于人体表型进行线粒体 DNA 疾病诊断
  11. 连连跨境支付独立站收款,最高90天提现0费率!
  12. 英语基本句型及一般时态
  13. 不要急,没有一朵花,从一开始就是花,也不要嚣张,没有一朵花,
  14. 微信三方平台调试过程中遇到的问题
  15. 五金模具设计常见的二十一块模板作用,一起学起来
  16. 苹果耳机AirPods Max(Pro)的空间音频技术,Unity端开源框架下载
  17. 啪!啪!给 JobIntentService 打针, Hilt 号的大针,看你爽不爽?哎呦,Espresso 看不到结果,用 UiAutomator 测。
  18. Ajax参数对照及Success内容
  19. 读CSV和狗血的分隔符问题,附解决方法!
  20. Java使用SWFTools转换PDF为SWF

热门文章

  1. [linux] 线程和wait命令,sleep命令
  2. C# 获取MAC地址
  3. linux如何修改主机名
  4. J2EE用监听器实现同一用户只能有一个在线
  5. C#制作图片压缩工具
  6. python时间处理
  7. 【工具类】发送邮件的方法
  8. winrar压缩指定目录
  9. 硬盘FAT32转NTFN格式的命令
  10. ideal中如何添加几个不同的项目在同一个idea页面显示(同一个窗口显示多个工程)...