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

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

发布网友 发布时间:2022-12-26 23:16

我来回答

1个回答

热心网友 时间:2023-06-22 22:10

  一 什么是线程冲突

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

  一个比较经典的例子是 用一个全局变量做计数器 然后开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/14294
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
erp可以看评论地址吗 淘宝评论url是什么意思? 揭秘:码牌支付风控升级,背后真相揭秘 电脑电视直播软件哪个好用什么软件好电脑看电视直播 潼南子同街学区是哪些 三极管BU406价格和参数? 火锅料放在冰柜忘了插电一个星期给会坏了吗 火锅的设备有哪些 火锅餐厅厨房有哪些 小火锅厨房设备有哪些 2006年2月的最后一天是几日 2月的最后一天一定是2月28日.___.(判断对错) 什么制图软件好用 信札趣谈·吴汝纶 吴汝纶《谕儿书》的翻译 口袋妖怪究极绿宝石耿鬼打霸主怎么配招 苹果电脑如何截取音乐? 怎么把胸变大啊 怎么能让女人的胸变大? 支付宝怎么查明细 形容悠闲的短句 1快乐大本营江疏影哪一期 百家姓`姓:傅的来源 赣州市中级人民法院二审判决书可以上诉吗 傅春荣的履历 赣州中院刑一庭在几楼层办公 赣州市中级法院执行局电话号码是什么 白天睡觉梦见葬礼梦见自己带着儿子穿梭在葬礼队伍中,并且到处买纸钱烧,但那些都是不认识的人,接二连三 梦见去送葬是什么意思 二维码重新设设置一个,嗯,不会有影响吧。 手机进水摄像头模糊了怎么办 番茄酱鱼的做法大全? 华为手机开不开机怎么把照片 小米黑鲨没激活丢了怎么找回 cad怎么把几条线连接 儿子三岁多了但是说话不清楚怎么办? 车辆保单怎么查,可以用手机查吗? 郑博士 十二生肖周运详解7.4-7.10 专插本的汉语言文学和考研的汉语言文学区别 专插本可以插那些专业? A3多大!用A4.怎么剪成A3 貂皮有灰尘怎么清理 铂士全合成机油咋样 惠州佳铂士建材有限公司过年放假几天 斯铂克是哪国的? 哪位大神会下Xp系统的PS? 电脑xp系统可以下载那个版本的ps?CS6版本的表示兼容不了 汪国真的经典现代诗 宜宾燃面的做法尤其是调制燃面的油里面有哪些配料 炸糖糕不鼓怎么回事 炸糖糕不鼓什么原因