问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

详解.NET编程过程中的线程冲突

发布网友 发布时间:2023-04-14 12:57

我来回答

1个回答

热心网友 时间:2023-10-12 00:52

  一 什么是线程冲突

  线程冲突其实就是指 两个或以上的线程同时对同一个共享资源进行操作而造成的问题

  一个比较经典的例子是 用一个全局变量做计数器 然后开N个线程去完成某个任务 每个线程完成一次任务就将计数器加一 直到完成 次任务 如果不考虑线程冲突问题 用类似下面的代码去做 则很可能会超额完成任务 线程越多 完成任务次数超出 次的可能性就越大

  伪代码如下

  

  int count = ;//全局计数器

  void ThreadMethod()//运行在每个线程的方法

  {

  while( true )

  {

  if ( count >= )//如果达到任务指标

  break;//中断线程执行

  DoSomething();//完成某个任务

  count++;

  }

  }

  //省略线程的创建等代码

  具体的 为什么会超额完成任务的原因在这里我就不赘述了 这个例子在单线程环境中是绝对不会超额完成任务的

  当然 在这个例子中 将count++放到if语句中 也许能降低一些事故发生的概率 但那不是绝对的 换言之这样的程序不能杜绝超额完成任务的可能

  其实从线程冲突的定义中我们不难发现 要造成线程冲突有两个必要条件 多线程和共享资源 这两个条件中有一个不成立 就不可能发生线程冲突问题

  所以 在单线程环境中 是不存在线程冲突的问题的 不过很可惜的是 我们的软件早已进化到了多进程多线程的时代 单线程的程序几乎是不存在的 无论是WinForm还是WebForm 程序运行的环境都是多线程的 而不论你自己是不是明确的开启了一个线程

  既然多线程是不可避免的 那么要避免线程冲突就只能从共享资源来开刀了

  二 线程安全的资源

  如果大家经常看MSDN或者VS帮助中的 NET类库参考的话 就不难发现几乎所有的类型都有这么一句话的描述 此类型的任何公共 static(在 Visual Basic中为 Shared) 成员都是线程安全的 但不保证所有实例成员都是线程安全的 那么线程安全到底是什么意思?

  其实线程安全很简单 就是指一个函数(方法 属性 字段或者别的)在同一时间被不同线程使用 不会造成任何线程冲突的问题 就说这个东西是线程安全的

  接下来来谈谈什么样的资源是线程安全的

  之所以使用资源这个词 是因为线程冲突不仅仅会发生在共享的变量上 两个线程同时对同一个文件进行读写 两个程序同时用同一个端口与同一个地址进行通信 都会造成线程冲突 只不过是操作系统和帮我们协调了这些冲突而已

  一个线程安全的资源即是指 在不同线程中使用不会导致线程冲突问题的资源

  一个不能被改变的资源是线程安全的 比如说一个常量

  

  const decimal pai = ;

  //C++: const double pai = ;

  因为pai的值不可能被改变 所以在不同的线程中使用也不会造成冲突 换言之它在不同的线程中同时被使用和在一个线程中被使用是没有区别的 所以这个东西线程安全的

  同样的 在 NET中 一个字符串的实例也是线程安全的 因为字符串的实例在 NET中也是不可以被改变的 一个字符串的实例一旦被创建 对其所有的属性 方法调用的结果都是唯一确定的 永远不会改变的 所以 NET类库参考中String类型才有 此类型是线程安全的 与之类似的Type类型 Assembly类型 都是线程安全的

  但string的实例是线程安全的 却不代表string的变量是线程安全的 换言之 假设有一个静态变量

  

  public static string str = ;

  str不是线程安全的 因为str这个变量的字符串实例可以被任何线程修改

  再考虑这样的例子

  public static readonly SqlConnection connection = new SqlConnection( connectionString );

  虽然connection本身虽然是线程安全的 但connection的任何成员都不是线程安全的

  比如说 我在一个线程中对这个connection调用了Open方法 然后进行查询操作 但在同一时刻 另一个线程调用了Close方法 这时候 就出现错误了

  但 单纯的使用connection而不使用其任何成员 比如说if ( connection != null )这样的代码 是不存在线程冲突的

  线程安全的资源其实还有很多 在此不一一赘述

  对于 NET Framework的类型的成员来说 只读的字段是线程安全的

  那么对于属性和方法来说 怎么知道是不是线程安全的?

  三 线程安全的函数

  因为属性和方法都是函数组成的 所以我们探讨一下什么是线程安全的函数

  上面我们说到 线程冲突的必要条件是多线程和共享资源 那么如果一个函数里面没有使用任何可能共享的资源 那么就不可能出现线程冲突 也就是线程安全的 比如说这样的函数

  

  public static int Add( int a int b )

  {

  return a + b;

  }

  这个函数中所使用的所有的资源都是自己的局部变量 而函数的局部变量是储存在堆栈上的 每个线程都有自己独立的堆栈 所以局部变量不可能跨线程共享 所以这样的函数显然是线程安全的

  但值得注意的是 下面的函数不是线程安全的

  

  public static void Swap( ref int a ref int b )

  //C++: void Swap( in& a int& b )

  {

  int c = a;

  a = b;

  b = c;

  }

  因为ref的存在 使得函数的参数是按引用传递进来的 换言之a和b看起来是函数的局部变量 但实际上却是函数外面的东西 如果这两个东西是另一个函数的局部变量 倒也没有问题 如果这两个东西是全局变量(静态成员) 就不能确保没有线程冲突了 而在上个例子中 a和b在传入函数之时 就做了一个拷贝的动作 所以传进来的a b到底是全局变量还是静态成员都没有关系了

  同样 这样的函数也不是线程安全的

  

  public static int Add( INumber a INumber b )

  //C++: int Add( INumber* a INumber* b );

  {

  return a Number + b Number;

  //C++: return a >Number + b >Number;

  }

  原因在于a和b虽然是函数的内部变量没错 但a Number和b Number却不是 它们不存在于堆栈上 而是在托管堆上 可能被其他线程更改

  但只使用局部变量的函数在 NET类库中是很少的 但 NET类库中还是有那么多线程安全的函数 是为什么呢?

  因为 即使一个函数使用了共享资源 如果其所使用的共享资源都是线程安全的 则这个函数也是线程安全的

  比如说这样的函数

  

  private const string connectionString = … ;

  public string GetConnectionString()

  {

  return connectionString;

  }

  虽然这个函数使用了一个共享资源connectionString 但因为这个资源是线程安全的 所以这个函数还是线程安全的

  同样的 我们可以得出 如果一个函数只调用线程安全的函数 只使用线程安全的共享资源

  那么这个函数也是线程安全的

  这里有一个容易被忽略的问题 运算符 并不是所有的运算符(尤其是重载后的运算符)都是线程安全的

  四 互斥锁

  有时候我们不得不面对线程不安全的问题 比如说在一开始提出来的那个例子 多线程完成 次任务 我们怎样才能解决这个问题 一个简单的办法就是给共享资源加上互斥锁 在C#中这很简单 比如一开始的那个例子

  

  public static class Environment

  {public static int count = ;//全局计数器

  }

  //…

  void ThreadMethod()//运行在每个线程的方法

  {

  while( true )

  {

  lock ( typeof( Environment ) )

  {

  if ( count >= )//如果达到任务指标

  break;//中断线程执行

  DoSomething();//完成某个任务

  count++;}}}

  通过互斥锁 使得一个线程在使用count字段的时候 其他所有的线程都无法使用 而被阻塞等待 达到了避免线程冲突的效果

  当然 这样的锁会使得这个多线程程序退化成同时只有一个线程在跑 所以我们可以把count++提前 使得lock的范围缩小 如这样

  

  void ThreadMethod()//运行在每个线程的方法

  {

  while( true )

  {

  lock ( typeof( Environment ) )

  {

  if ( count++ >= )//如果达到任务指标

  break;//中断线程执行

  }

  DoSomething();//完成某个任务

  }}

  最后来聊聊SyncRoot的问题

  用 NET的一定会有很多朋友困惑 为什么对一个容器加锁 需要这样写

  lock( Container SyncRoot )

  而不是直接lock( Container )

  因为锁定一个容器并不能保证不会对这个容器进行修改 考虑这样一个容器

  

  public class Collection

  {

  private ArrayList _list;

  public Add( object item )

  {

  _list Add( item );

  }

  public object this[ int index ]

  {

  get { return _list[index]; }

  set { _list[index] = value;}

  }}

lishixin/Article/program/net/201311/13071
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
手机导航地图语音怎么下载 如何分别真金和仿金首饰 怎样区分真金和仿金首饰呢 小学生新年晚会主持人的串词!!(不要太多)急 大大后天就需要了!!!_百度... 周年晚会策划公司 奥格瑞玛传送门大厅在哪 奥格瑞玛传送门大厅怎么走 锻炼颈椎的几个动作 水多久能结冰 冰能在多长时间内形成 请问水低于0度会结冰吗? 如何防止脱发严重 newdelete线程安全吗 C library 不是线程安全 是什么意思? 什么叫线程安全? 当代中国学校德育现状 关于开展全国中小学生安全教育日活动的通知 德育》是核心期刊吗 40千瓦光伏需多大空开 北师大版五年级上册语文《水就是生命》教案三篇 幼儿园大班《节约用水》教案10篇 三年级科学水的实验说课稿范文2020 东莞下桥站坐地铁到深圳华强北多久 地铁下桥到厚街会展中心要座几站 东莞西站到下桥有地铁吗 深圳大新地铁站到东莞下桥地铁站坐哪条路线 东莞下桥地铁站到下桥车管所有多远 下桥地铁站到万科运东一号怎么走 下桥地铁站开通了吗 下桥地铁站到虎门高铁站要多久 挑踢的意思挑踢的意思是什么 新QQ空间怎么换背景颜色 非钻 戍将的解释 锤子手机system无响应无法按动屏幕 考编如何报名 75x91k和75z9f哪个好 海南海发控股集团有限公司怎么样? 九号(海南)控股有限公司电话是多少? 海口新海交通枢纽站业主是哪家单位 电视机测屏方法 压岁钱的来源和寓意手抄报 手抄报图片大全 怎么找回家里的wifi密码? 压岁钱的来源和意义手抄报 手抄报图片大全 WIN7硬件加速默认是全开的吗? 老街口南瓜籽那产的 晒晒我们班的牛人优秀作文600字 注销了的怎么恢复? 被注销怎么找回来? 被注销怎么找回来? 名人传读后感600字左右初二 2022年广东汕头市公安局公开招聘警务辅助人员公告 2022年汕头市事业编考试时间 汕头市疾病预防控制中心公开招聘工作人员公告