编程博客
002、修改Nuget包位置
001、C#常用的单词
003、收藏的书签
回家准备
004、ASP.NET Core 3.0 gRPC
001、ASP.NET Core 3.0 使用gRPC
002、ASP.NET Core 3.0 gRPC 双向流
003、ASP.NET Core 3.0 gRPC 身份认证和授权
005、飞牛NAS
001、 FnOS飞牛系统实用配置1-应用远程访问(迅雷)
006、DeepSeek
001、DeepSeek 使用指南
007、并发编程
01、并发编程 - 死锁的产生、排查与解决方案
02、并发编程 - 初识线程
03、并发编程 - 线程浅试
04、并发编程 - 线程同步(一)
05、并发编程 - 线程同步(二)
06、并发编程 - 线程同步(三)之原子操作 Interlocked 简介
07、并发编程 - 线程同步(四)之原子操作 Interlocked 详解一
08、并发编程 - 线程同步(五)之原子操作 Interlocked 详解二
09、并发编程 - 线程同步(六)之锁 lock
008、启动项
03、应用--Program 中的 WebApplication
02、主机--Host
01、Program 文件的作用
04、控制反转 IOC 与依赖注入 DI
05、中间件
06、Logger 原理及配置 Log4Net
07、ElasticSearch
本文档使用 MrDoc 发布
-
+
首页
07、并发编程 - 线程同步(四)之原子操作 Interlocked 详解一
上一章我们了解了原子操作 Interlocked 类的设计原理及简单介绍,今天我们将对 Interlocked 的使用进行详细讲解。  在此之前我们先学习一个概念——原子操作。 # ***01***、Read 方法 该方法用于原子的读取 64 位值,有分别针对 long 类型和 ulong 类型的两个重载方法; 对于 64 位系统,64 位数据类型的读取本身就是原子操作;而对于 32 位系统,64 位数据类型的读取需要至少两个原子指令,因此在 32 位系统可以通过 Read 方法对 64 位数据类型进行原子读取。 用法也很简单,示例如下: ```csharp private static long _readValue = 0; public static void ReadRun() { var thread = new Thread(ModifyReadValue); thread.Start(); Thread.Sleep(100); var value = Interlocked.Read(ref _readValue); Console.WriteLine("原子读取long类型变量: " + value); } static void ModifyReadValue() { _readValue = 88; Console.WriteLine("修改long类型变量: " + _readValue); } ``` 运行结果如下:  因为系统环境原因无法模拟出 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 次观察每次运行结果,代码如下: ```csharp private static long _incrementValue = 0; public static void IncrementRun() { //运行9次测试,观察每次结果 for (var i = 1; i < 10; i++) { //启动100个线程,对变量进行递增 var threads = new Thread[100]; for (var j = 0; j < threads.Length; j++) { threads[j] = new Thread(ModifyIncrementValue); threads[j].Start(); } //等待所有线程执行完成 foreach (var thread in threads) { thread.Join(); } //最后打印结果 Console.WriteLine($"第 {i} 运行结果: {_incrementValue}"); _incrementValue = 0; } } static void ModifyIncrementValue() { for (var i = 0; i < 1000; i++) { ++_incrementValue; } } ``` 先看下执行结果:  可以发现每次的运行结果都不相同,并且结果也不对。这就是因为 ++i 操作并不是原子操作,是线程不安全的。 只需要把上面代码: ```csharp ++_incrementValue; ``` 改为: ```csharp 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 方法中的示例,自己写一个线程不安全的示例。 ```csharp private static long _addValue = 0; public static void AddRun() { for (var j = 0; j < 1000; j++) { //_addValue =_ addValue + j; Interlocked.Add(ref _addValue, j); } Console.WriteLine($"累加结果: {_addValue}"); _addValue = 0; for (var j = 0; j < 1000; j++) { //_addValue =_ addValue - j; Interlocked.Add(ref _addValue, -j); } Console.WriteLine($"累减结果: {_addValue}"); } ``` 执行结果如下: 
个人天使
2025年2月10日 10:27
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码