C# 中的多线程同步:原子变量、原子操作、内存顺序和可见性
C# 中的多线程同步:原子变量、原子操作、内存顺序和可见性
引言
随着现代计算机系统的发展,多核处理器已经变得非常普遍。在这种环境下,多线程编程成为提高应用程序性能的关键技术之一。然而,多线程编程带来了新的挑战,其中之一就是确保数据在并发访问时的一致性和安全性。本文将探讨 C# 中的多线程同步机制,特别是原子变量、原子操作、内存顺序和可见性,并通过代码示例来演示如何使用这些工具来构建健壮的并发程序。
原子操作与原子变量
在多线程环境中,原子操作是指那些不可中断的操作。这意味着一旦开始执行,该操作就会一直执行到完成,期间不会被其他线程中断。C# 中提供了多种工具来实现原子操作,包括 Interlocked 类和 System.Threading.Atomic 类。
Interlocked 类
Interlocked 类提供了多个静态方法来执行原子操作,这些方法可以确保在多线程环境中对整型变量的操作是原子的。例如,Interlocked.Increment 和 Interlocked.Decrement 可以用于安全地增加或减少共享变量的值。
示例代码
using System;
using System.Threading;class Program
{private static int counter = 0;private static CountDownEvent allDone = new CountDownEvent(5); // 五个线程static void Increment(){for (int i = 0; i < 100000; i++){Interlocked.Increment(ref counter);}allDone.Signal(); // 通知检查线程,本线程已完成递增}static void Check(){allDone.Wait(); // 等待所有线程完成int finalValue = counter;Console.WriteLine($"Final counter value: {finalValue}");// 验证最终值是否等于预期int expectedValue = 5 * 100000; // 五个线程,每个线程递增100000次if (finalValue == expectedValue){Console.WriteLine("Counter incremented correctly.");}else{Console.WriteLine("Counter did not increment correctly.");}}static void Main(string[] args){var threads = new Thread[5];for (int i = 0; i < 5; i++){threads[i] = new Thread(new ThreadStart(Increment));threads[i].Start();}var checkThread = new Thread(new ThreadStart(Check));checkThread.Start();foreach (var thread in threads){thread.Join();}checkThread.Join();}
}
System.Threading.Atomic 类
从 C# 8.0 开始,引入了 System.Threading.Atomic 类,该类提供了原子类型的实现,类似于 C++ 中的 std::atomic。使用 System.Threading.Atomic 类可以更加方便地处理原子操作。
示例代码
using System;
using System.Threading;class Program
{private static int counter = 0;private static CountDownEvent allDone = new CountDownEvent(5); // 五个线程static void Increment(){for (int i = 0; i < 100000; i++){Interlocked.Increment(ref counter);}allDone.Signal(); // 通知检查线程,本线程已完成递增}static void Check(){allDone.Wait(); // 等待所有线程完成int finalValue = counter;Console.WriteLine($"Final counter value: {finalValue}");// 验证最终值是否等于预期int expectedValue = 5 * 100000; // 五个线程,每个线程递增100000次if (finalValue == expectedValue){Console.WriteLine("Counter incremented correctly.");}else{Console.WriteLine("Counter did not increment correctly.");}}static void Main(string[] args){var threads = new Thread[5];for (int i = 0; i < 5; i++){threads[i] = new Thread(new ThreadStart(Increment));threads[i].Start();}var checkThread = new Thread(new ThreadStart(Check));checkThread.Start();foreach (var thread in threads){thread.Join();}checkThread.Join();}
}
内存顺序和可见性
在多线程环境中,内存顺序和可见性是非常重要的概念。内存顺序指的是内存操作的顺序,而可见性则确保一个线程对共享数据的修改对其他线程可见。
内存顺序
内存顺序决定了内存操作的执行顺序,这对于确保数据的一致性至关重要。在 C# 中,Interlocked 类提供了不同的内存顺序选项,如 MemoryOrderRelease、MemoryOrderAcquire、MemoryOrderSeqCst 等。
示例代码
using System;
using System.Threading;class Program
{private static int flag = 0;private static int counter = 0;static void Writer(){Interlocked.Exchange(ref flag, 1, MemoryOrder.Release); // 设置 flagInterlocked.Exchange(ref counter, 42, MemoryOrder.Release); // 设置 counter}static void Reader(){while (Interlocked.CompareExchange(ref flag, 0, 1, MemoryOrder.Acquire) != 1){Thread.Yield(); // 使当前线程放弃执行权}Console.WriteLine("Counter value: " + Interlocked.Exchange(ref counter, 0, MemoryOrder.Acquire));}static void Main(string[] args){var writerThread = new Thread(Writer);var readerThread = new Thread(Reader);writerThread.Start();readerThread.Start();writerThread.Join();readerThread.Join();Console.WriteLine("Final counter value: " + counter);}
}
内存可见性
内存可见性确保一个线程对共享数据的修改对其他线程可见。在 C# 中,使用 volatile 关键字可以标记一个变量,确保编译器不会对该变量进行优化,从而保证在多线程环境中的内存可见性。但是,volatile 本身并不提供原子性,仅保证内存可见性。
示例代码
using System;
using System.Threading;class Program
{private static volatile bool flag = false;private static int counter = 0;static void Writer(){flag = true;counter = 42;}static void Reader(){while (!flag){Thread.Yield(); // 使当前线程放弃执行权}Console.WriteLine("Counter value: " + counter);}static void Main(string[] args){var writerThread = new Thread(Writer);var readerThread = new Thread(Reader);writerThread.Start();readerThread.Start();writerThread.Join();readerThread.Join();Console.WriteLine("Final counter value: " + counter);}
}
结论
多线程编程需要仔细考虑数据的一致性和同步问题。C# 提供了多种工具来帮助开发者构建健壮的并发程序,包括 Interlocked 类、System.Threading.Atomic 类以及 volatile 关键字。通过合理使用这些工具,可以有效地避免数据竞争和其他并发问题,确保程序的正确性和高效性。
通过上述示例和解释,我们看到了如何在 C# 中使用原子变量、原子操作、内存顺序和可见性来构建可靠的多线程应用程序。希望这篇文章能帮助你在开发并发程序时更好地理解和运用这些概念。
相关文章:
C# 中的多线程同步:原子变量、原子操作、内存顺序和可见性
C# 中的多线程同步:原子变量、原子操作、内存顺序和可见性 引言 随着现代计算机系统的发展,多核处理器已经变得非常普遍。在这种环境下,多线程编程成为提高应用程序性能的关键技术之一。然而,多线程编程带来了新的挑战ÿ…...
视图(mysql)
一、什么是视图 视图是⼀个虚拟的表,它是基于⼀个或多个基本表或其他视图的查询结果集。视图本⾝不存储数 据,⽽是通过执⾏查询来动态⽣成数据。⽤⼾可以像操作普通表⼀样使⽤视图进⾏查询、更新和管 理。视图本⾝并不占⽤物理存储空间,它仅…...
elementui组件el-upload实现批量文件上传
el-upload组件上传文件时,每传一个文件会调一次接口,所以当上传多个文件的时候,有 n 个文件就要调 n 次接口。 刚好之前工作中遇到使用el-upload组件批量上传文件的需求,来看看怎么实现。 思路: 1.取消组件的自动上…...
【JAVA入门】Day45 - 压缩流 / 解压缩流
【JAVA入门】Day45 - 压缩流 / 解压缩流 文章目录 【JAVA入门】Day45 - 压缩流 / 解压缩流一、解压缩流二、压缩流 在文件传输过程中,文件体积比较大,传输较慢,因此我们发明了一种方法,把文件里的数据压缩到一种压缩文件中&#x…...
Qt_自定义信号
目录 1、自定义信号的规定 2、创建自定义信号 3、带参数的信号与槽 4、一个信号连接多个槽 5、信号与槽的断开 结语 前言: 虽然Qt已经内置了大量的信号,并且这些信号能够满足大部分的开发场景,但是Qt仍然允许开发者自定义信号&#…...
【运维方案】某系统运维需求方案参考(doc全原件2024)
系统运维需求方案 1服务目标 2服务人力需求、服务资源需求 3信息资产统计服务需求 4业务应用软件服务需求 5网络、安全系统运维服务需求 6主机、存储系统运维服务需求 7数据库系统运维服务需求 8终端运维服务需求 9综合布线系统服务需求 10大屏幕显示系统的维护需求 11视频会议…...
Linux环境使用Git同步教程
📖 前言:由于CentOS 7已于2024年06月30日停止维护,为了避免操作系统停止维护带来的影响,我们将把系统更换为Ubuntu并迁移数据,在此之前简要的学习Git的上传下载操作。 目录 🕒 1. 连接🕘 1.1 配…...
c++临时对象导致的生命周期问题
对象的生命周期是c中非常重要的概念,它直接决定了你的程序是否正确以及是否存在安全问题。 今天要说的临时变量导致的生命周期问题是非常常见的,很多时候没有一定经验甚至没法识别出来。光是我自己写、review、回答别人的问题就犯了或者看到了许许多多这…...
CSP-J 算法基础 深度优先搜索
文章目录 前言深度优先搜索通俗解释例子深度优先搜索的步骤DFS 的特点生活中的类比 为什么递归问题会变成深度优先搜索?递归与深度优先搜索的关系:递归与系统栈递归调用的过程:栈的作用: 递归与系统栈的简单示例递归实现 DFS 的简…...
LeetCode题练习与总结:基本计算器 Ⅱ--227
一、题目描述 给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。 整数除法仅保留整数部分。 你可以假设给定的表达式总是有效的。所有中间结果将在 [-2^31, 2^31 - 1] 的范围内。 注意:不允许使用任何将字符串作为数学表达式计算…...
Elasticsearch基础(七):Logstash如何开启死信队列
文章目录 Logstash如何开启死信队列 一、确保 Elasticsearch 输出插件启用 DLQ 支持 二、配置 Logstash DLQ 设置 三、查看死信队列 四、排查 CSV 到 Elasticsearch 数据量不一致的问题 Logstash如何开启死信队列 在 Logstash 中,死信队列(Dead Le…...
c语言--力扣简单题目(链表的中间节点)讲解
题目如下: 给你单链表的头结点 head ,请你找出并返回链表的中间结点。 如果有两个中间结点,则返回第二个中间结点。 示例 1: 输入:head [1,2,3,4,5] 输出:[3,4,5] 解释:链表只有一个中间结点…...
【STM32 Blue Pill编程】-定时器计数模式
定时器计数模式 文章目录 定时器计数模式1、定时器计数模式介绍2、硬件准备及接线3、模块配置3.1 定时器计数模式配置3.2 定时器中断配置3.3 串口配置4、代码实现在本文中,我们将讨论如何在计数器模式下配置 STM32 Blue Pill 定时器模块。 要将定时器用作计数器,我们将其配置…...
【例题】lanqiao1331 二进制中 1 的个数
二进制中 1 的个数 题目描述 给定一个整数 x,输出该数二进制表示中 1 的个数。 例:9 的二进制表示为 1001,有 2 位是 1 ,所以函数返回 2。 输入描述 输入 x (内存空间为 32 位的整数)。 输出描述 第一…...
【论文解读】图像序列识别:CRNN技术在场景文本识别中的应用与突破(附论文地址)
论文地址:https://arxiv.org/pdf/1507.05717 这篇文章的标题是《An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition》,作者是Baoguang Shi, Xiang Bai和Cong Yao,…...
Vue3+CesiumJS相机定位camera
new Cesium.Camera (scene) 摄像机由位置,方向和视锥台定义。 方向与视图形成正交基准,上和右视图x上单位矢量。 视锥由6个平面定义。每个平面都由 Cartesian4 对象表示,其中x,y和z分量定义垂直于平面的单位矢量,w分量…...
turbo译码算法MAX, MAX_SCALE and MAX_STAR的比较
在Turbo码的译码算法中,MAX、MAX_SCALE和MAX_STAR是涉及对数似然比(LLR)计算时,对MAP(最大后验概率)算法或其变种Log-MAP算法中分支度量计算的几种不同处理方式。下面是对这三种方法的比较: 1.…...
关于HarmonyOS的学习
day31 购物车案例 一、加入购物车 1、点击按钮后,把当前这个列表的数据拿到,应该存储到一个数组里面 --- 数据结构,把数据存储进行数组2、假如已经把所有的数据添加数组完毕,最终应该存储进购物车里面,所谓的购物车说…...
【雅特力AT32】搭建模板工程及GPIO点灯操作
目录 AT32模板工程建立及点灯操作 建立AT32模板工程 AT32点灯操作 LED原理图GPIO寄存器LED源码分析 建立AT32模板工程 从0到编译运行详细搭建保姆教程: 【雅特力AT32】Keil 环境:搭建标准库模板工程、使用 AT-Link、Debug 里选择 CMSIS-DAP调试器 下面做…...
实战千问2大模型第三天——Qwen2-VL-7B(多模态)视频检测和批处理代码测试
画面描述:这个视频中,一位穿着蓝色西装的女性站在室内,背景中可以看到一些装饰品和植物。她双手交叉放在身前,面带微笑,似乎在进行一场演讲或主持活动。她的服装整洁,显得非常专业和自信。 一、简介 阿里通义千问开源新一代视觉语言模型Qwen2-VL。其中,Qwen2-VL-72B在大…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
