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

简介: 原文:.NET 并行(多核)编程系列之七 共享数据问题和解决概述.NET 并行(多核)编程系列之七 共享数据问题和解决概述   前言:之前的文章介绍了了并行编程的一些基础的知识,从本篇开始,将会讲述并行编程中实际遇到一些问题,接下来的几篇将会讲述数据共享问题。
原文: .NET 并行(多核)编程系列之七 共享数据问题和解决概述

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

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

 

 本篇的议题如下:      

  1. 数据竞争
  2. 解决方案提出
  3. 顺序的执行解决方案
  4. 数据不变解决方案

 

  系列文章链接:

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

  .NET 4 并行(多核)编程系列之二 从Task开始 

  .NET 4 并行(多核)编程系列之三 从Task的取消 

  .NET 4 并行(多核)编程系列之四 Task的休眠 

  .NET 并行(多核)编程系列之五 Task执行和异常处理 

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

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

 

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

 

代码
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();
    }
}

 

在上面的例子中,创建了10task,每个task都是把BankAccount.Balance自增1000次。之后代码就等到10task执行完毕,然后打印出Balance的值。大家猜想一下,上次的代码执行完成之后,打印出来的Balance的结果是多少?

 

J结果确实和大家猜想的一样:结果不等于10000。每次执行一次上面的代码,都会得到不同的结果,而且这些结果值都在10000左右,如果运气好,可能看到有那么一两次结果为10000.为什么会这样?

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

 

  1. 数据竞争

如果大家对多线程编程比较熟悉,就知道上面情况的产生是因为共享数据竞争导致的(对多线程不熟悉不清楚的朋友也不用担心)。当有两个或者更多的task在运行并且操作同一个共享公共数据的时候,就存在潜在的竞争。如果不合理的处理竞争问题,就会出现上面意想不到的情况。

下面就来分析一下:上面代码的情况是怎么产生的。

当在把account对象的Balance进行自增的时候,一般执行下面的三个步骤:

  1. 读取现在account对象的Balance属性的值。
  2. 计算,创建一个临时的新变量,并且把Balance属性的值赋值给新的变量,而且把新变量的值增加1
  3. 把新变量的值再次赋给accountBalance属性

 

在理论上面,上面的三个步骤是代码的执行步骤,但是实际中,由于编译器,.NET 运行时对自增操作的优化操作,和操作系统等的因素,在执行上面代码的时候,并不一定是按照我们设想的那样运行的,但是为了分析的方便,我们还是假设代码是按照上面的三个步骤运行的。

之前的代码每次执行一次,执行代码的计算机就每次处于不同的状态:CPU的忙碌状况不同,内存的剩余多少不同,等等,所以每次代码的运行,计算机不可能处于完全一样的环境中。

 

在下面的图中,显示了两个task之间是如何发生竞争的。当两个task启动了之后(虽然说是并行运算,但是不管这样,两个的task的执行时间不可能完全一样,也就是说,不可能恰好就是同时开始执行的,起码在开始执行的时间上是有一点点的差异的)。

 

 

1.    首先Task1读取到当前的balance的值为0

2.    然后,task2运行了,并且也读取到当前的balance值为0

3.    两个task都把balance的值加1

4.    Task1balance的值加1后,把新的值保存到了balance

5.    Task2 也把新的保存到了balance

所以,结果就是:虽然两个task 都为balance1,但是balance的值还是1

通过这个例子,相信大家应该清楚,为什么上面的10task执行1000,而执行后的结果不是10000了。 

 

 

2.  解决方案提出

 

数据竞争就好比一个生日party。其中,每一个task都是参加party的人,当生日蛋糕出来之后,每个人都兴奋了。如果此时,所有的人都一起冲过去拿属于他们自己的那块蛋糕,此时party就一团糟了,没有如何顺序。

在之前的图示例讲解中,balance那个属性就好比蛋糕,因为task1task2都要得到它,然后进行运算。当我们来让多个task共享一个数据时就可能出现问题。下面列出了四种解决方案:

1.    顺序执行:也就是让第一个task执行完成之后,再执行第二个。

2.    数据不变:我们让task不能修改数据。

3.    隔离:我们不共享数据,让每个task都有一份自己的数据拷贝。

4.    同步:通过调整task的执行,有序的执行task

注意:同步和以前多线程中的同步,或者数据库操作时的同步概念不一样

 

 

3. 顺序的执行的解决方案

顺序的执行解决了通过每次只有一个task访问共享数据的方式解决了数据竞争的问题,其实在本质上,这种解决方案又回到了之前的单线程编程模型。如果拿之前的party分蛋糕的例子,那么现在就是一次只能允许一个人去拿蛋糕。

 

  1. 数据不变解决方案

 

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

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();
    }
}

 

 

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

 

今天暂时就写到这里,谢谢大家。

 

   版权为小洋和博客园所有,转载请标明出处给作者。

   http://www.cnblogs.com/yanyangtian

目录
相关文章
|
4月前
|
传感器 数据采集 物联网
探索.NET nanoFramework:为嵌入式设备编程的新途径
探索.NET nanoFramework:为嵌入式设备编程的新途
125 7
|
4月前
|
SQL XML 关系型数据库
入门指南:利用NHibernate简化.NET应用程序的数据访问
【10月更文挑战第13天】NHibernate是一个面向.NET的开源对象关系映射(ORM)工具,它提供了从数据库表到应用程序中的对象之间的映射。通过使用NHibernate,开发者可以专注于业务逻辑和领域模型的设计,而无需直接编写复杂的SQL语句来处理数据持久化问题。NHibernate支持多种数据库,并且具有高度的灵活性和可扩展性。
70 2
|
6月前
|
大数据 开发工具 开发者
从零到英雄:.NET核心技术带你踏上编程之旅,构建首个应用,开启你的数字世界探险!
【8月更文挑战第28天】本文带领读者从零开始,使用强大的.NET平台搭建首个控制台应用。无论你是新手还是希望扩展技能的开发者,都能通过本文逐步掌握.NET的核心技术。从环境搭建到创建项目,再到编写和运行代码,详细步骤助你轻松上手。通过计算两数之和的小项目,你不仅能快速入门,还能为未来开发更复杂的应用奠定基础。希望本文为你的.NET学习之旅开启新篇章!
56 1
|
6月前
|
开发框架 .NET 数据库连接
闲话 Asp.Net Core 数据校验(三)EF Core 集成 FluentValidation 校验数据例子
闲话 Asp.Net Core 数据校验(三)EF Core 集成 FluentValidation 校验数据例子
114 1
|
6月前
|
存储 C#
揭秘C#.Net编程秘宝:结构体类型Struct,让你的数据结构秒变高效战斗机,编程界的新星就是你!
【8月更文挑战第4天】在C#编程中,结构体(`struct`)是一种整合多种数据类型的复合数据类型。与类不同,结构体是值类型,意味着数据被直接复制而非引用。这使其适合表示小型、固定的数据结构如点坐标。结构体默认私有成员且不可变,除非明确指定。通过`struct`关键字定义,可以包含字段、构造函数及方法。例如,定义一个表示二维点的结构体,并实现计算距离原点的方法。使用时如同普通类型,可通过实例化并调用其成员。设计时推荐保持结构体不可变以避免副作用,并注意装箱拆箱可能导致的性能影响。掌握结构体有助于构建高效的应用程序。
188 7
|
6月前
|
Java Spring 自然语言处理
Spring 框架里竟藏着神秘魔法?国际化与本地化的奇妙之旅等你来揭开谜底!
【8月更文挑战第31天】在软件开发中,国际化(I18N)与本地化(L10N)对于满足不同地区用户需求至关重要。Spring框架提供了强大支持,利用资源文件和`MessageSource`实现多语言文本管理。通过配置日期格式和货币符号,进一步完善本地化功能。合理应用这些特性,可显著提升应用的多地区适应性和用户体验。
65 0
|
7月前
|
开发框架 JSON 前端开发
利用查询条件对象,在Asp.net Web API中实现对业务数据的分页查询处理
利用查询条件对象,在Asp.net Web API中实现对业务数据的分页查询处理
|
6月前
|
传感器 数据采集 物联网
探索未来:.NET nanoFramework引领嵌入式设备编程革新之旅
【8月更文挑战第28天】.NET nanoFramework 是一款专为资源受限的嵌入式设备设计的轻量级、高性能框架,基于 .NET Core,采用 C# 进行开发,简化了传统底层硬件操作的复杂性,极大提升了开发效率。开发者可通过 Visual Studio 或 Visual Studio Code 快速搭建环境并创建项目,利用丰富的库和驱动程序轻松实现从基础 LED 控制到网络通信等多种功能,显著降低了嵌入式开发的门槛。
122 0
|
6月前
|
开发框架 前端开发 算法
分享 .NET EF6 查询并返回树形结构数据的 2 个思路和具体实现方法
分享 .NET EF6 查询并返回树形结构数据的 2 个思路和具体实现方法
110 0

热门文章

最新文章