并发编程 - 线程同步(四)之原子操纵Interlocked详解一 ...

tsx81428  金牌会员 | 2025-2-12 15:30:24 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 843|帖子 843|积分 2531

上一章我们了解了原子操纵Interlocked类的设计原理及简单介绍,本日我们将对Interlocked的使用进行详细讲解。

在此之前我们先学习一个概念——原子操纵。
01、Read方法

该方法用于原子的读取64位值,有分别针对long类型和ulong类型的两个重载方法;
对于64位系统,64位数据类型的读取本身就是原子操纵;而对于32位系统,64位数据类型的读取须要至少两个原子指令,因此在32位系统可以通过Read方法对64位数据类型进行原子读取。
用法也很简单,示例如下:
  1. private static long _readValue = 0;
  2. public static void ReadRun()
  3. {
  4.     var thread = new Thread(ModifyReadValue);
  5.     thread.Start();
  6.     Thread.Sleep(100);
  7.     var value = Interlocked.Read(ref _readValue);
  8.     Console.WriteLine("原子读取long类型变量: " + value);
  9. }
  10. static void ModifyReadValue()
  11. {
  12.     _readValue = 88;
  13.     Console.WriteLine("修改long类型变量: " + _readValue);
  14. }
复制代码
运行效果如下:

由于系统环境缘故原由无法模拟出32位系统效果,因此这里只是给了个简单使用示例。
02、Increment方法

该方法用于原子的递增指定的变量,并返回递增后的新值。该方法有4个重载方法,分别为long、ulong、int和uint四种数据类型;该方法适用于多线程环境中须要安全递增变量的场景,如计数器、资源管理等。
对于加法操纵,无论是i+1,还是i++或++i,都不是线程安全的,最终可能会天生3条CPU指令,整个操纵过程大致如下:
1.将 i 的值加载到寄存器,即从内存中读取i;
2.将寄存器中值加1,即i值加1;
3.最后将寄存器中值回写到i,即完成i值的变更;
而在这编码层面为1行代码,而CPU层面为3行指令的操纵中,随时都有可能被线程调理器打断,而导致其他线程同时对i进行操纵,最终导致竞争条件,最后数据庞杂。
下面我们来举个例子,启动100个线程,分别对一个共享变量进行1000次递增1,最后打印出共享变量,运行这个示例9次观察每次运行效果,代码如下:
  1. private static long _incrementValue = 0;
  2. public static void IncrementRun()
  3. {
  4.     //运行9次测试,观察每次结果
  5.     for (var i = 1; i < 10; i++)
  6.     {
  7.         //启动100个线程,对变量进行递增
  8.         var threads = new Thread[100];
  9.         for (var j = 0; j < threads.Length; j++)
  10.         {
  11.             threads[j] = new Thread(ModifyIncrementValue);
  12.             threads[j].Start();
  13.         }
  14.         //等待所有线程执行完成
  15.         foreach (var thread in threads)
  16.         {
  17.             thread.Join();
  18.         }
  19.         //最后打印结果
  20.         Console.WriteLine($"第 {i} 运行结果: {_incrementValue}");
  21.         _incrementValue = 0;
  22.     }
  23. }
  24. static void ModifyIncrementValue()
  25. {
  26.     for (var i = 0; i < 1000; i++)
  27.     {
  28.         ++_incrementValue;
  29.     }
  30. }
复制代码
先看下执行效果:

可以发现每次的运行效果都不相同,而且效果也不对。这就是由于++i操纵并不是原子操纵,是线程不安全的。
只须要把上面代码:
  1. ++_incrementValue;
复制代码
改为:
  1. Interlocked.Increment(ref _incrementValue);
复制代码
即可解决上面的问题,修改事后,我们再来看看执行效果:

03、Decrement方法

该方法用于原子的递减指定的变量,并返回递减后的新值。该方法同样有4个重载方法,分别为long、ulong、int和uint四种数据类型;
该方法和Increment方法基本一样,区别就是一个是递增一个是递减,因此用法可以直接参考Increment方法,这里就不做详细讲解了。
04、Add方法

该方法用于原子的对两个变量求和,将第一个变量替换为两者和,并返回操纵后第一个变量的新值。该方法同样有4个重载方法,分别为long、ulong、int和uint四种数据类型;
虽然这个方法叫求和是加法,但是只须要把第2个参数变为负数,既可以实现减法。简单来说该方法可以实现原子的对两个变量求和与求差。
上面Increment方法和Decrement方法,只能对变量每次进行递增递减1,而能随意加减,可以通过Add方法实现两个变量进行加减。
下面我们用代码实现累加和累减示例用来阐明Add使用方法,就不展示线程安全差异了,可以参考Increment方法中的示例,自己写一个线程不安全的示例。
  1. private static long _addValue = 0;
  2. public static void AddRun()
  3. {
  4.     for (var j = 0; j < 1000; j++)
  5.     {
  6.         //_addValue =_ addValue + j;
  7.         Interlocked.Add(ref _addValue, j);
  8.     }
  9.     Console.WriteLine($"累加结果: {_addValue}");
  10.     _addValue = 0;
  11.     for (var j = 0; j < 1000; j++)
  12.     {
  13.         //_addValue =_ addValue - j;
  14.         Interlocked.Add(ref _addValue, -j);
  15.     }
  16.     Console.WriteLine($"累减结果: {_addValue}");
  17. }
复制代码
执行效果如下:

:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

tsx81428

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表