并发编程产生的根本原因和C#怎么处理并发问题?

并发编程产生的根本原因和C#怎么处理并发问题?

前言

对于现在很多编程语言来说,多线程已经得到了很好的支持,

以至于我们写多线程程序简单,但是一旦遇到并发产生的问题就会各种尝试。

因为不是明白为什么会产生并发问题,并发问题的根本原因是什么。

接下来就让我们来走近一点并发产生的那些问题。

猜猜是多少?

 public class ThreadTest_V0
    {
        public int count = 0;
        public void Add1()
        {
            int index = 0;
            while (index++ < 1000000)//100万次
            {
                ++count;
            }
        }

        public void Add2()
        {
            int index = 0;
            while (index++ < 1000000)//100万次
            {
                count++;
            }
        }
    }

结果是多少?

static void V0()
        {
            ThreadTest_V0 testV0 = new ThreadTest_V0();
            Thread th1 = new Thread(testV0.Add1);
            Thread th2 = new Thread(testV0.Add2);

            th1.Start();
            th2.Start();
            th1.Join();
            th2.Join();

            Console.WriteLine($"V0:count = {testV0.count}");
        }

答案:100万 到 200万之间的随机数。

为什么?

接下来我们去深入了解一下为什么会这样?

一、可见性

首先我们来到 “可见性” 这个陌生的词汇身边。

通过一番交谈了解到:

对可见性进行一下总结就是我改的东西你能同时看到。

1.1 背景

解读一下呢,就像下面这样:

CPU 内存 硬盘 ,处理速度上存在很大的差距,为了弥补这种差距,也是为了利用CPU强大计算能力。

CPU 和内存之前加入了缓存,就是我们经常听说的 寄存器缓存、L1、2、3级缓存。

应该的处理流程是这样的:读取内存数据,缓存到CPU缓存中,CPU进行计算后,从CPU缓存中写回内存。

1.2 线程切换

还有一点 我们都知道多线程其实是通过切换时间片来达到 “同时” 处理问题的假象。

线程切换
在这里插入图片描述

1.3 单核时代

你也发现了,对于单核来说,程序其实还是串行开发的。
在这里插入图片描述

单核CPU

就像是 “一个人” ,东干点,西干点,如果切换频率上再快点速度,比我们的眨眼时间还短呢?那…… 接下来,我们进入了多核时代。

1.4多核时代

顾名思义,多个CPU,也就是每个CPU核心都有自己的缓存体系,但是内存只有一份。

比如CPU就是我么们的本地缓存,而内存相当于数据库。

我们每个人的本地缓存极有可能是不一样的,如果我们拿着这些缓存直接做一些业务计算,

结果可想而知,多核时代,多线程并发也会有这样的问题 — CPU缓存的数据不一样咋办?
在这里插入图片描述

多核CPU

1.5 volatile

这是CLR 为我们提出的解决方案,就是在遇到可见性引发的并发问题时,使用 volatile 关键字。

就是告诉 CPU,我不想用你的缓存,所有的请求都直接读写内存。

一句话,就是禁用缓存。

看上去这样就能解决并发问题了吧?也不全是,还有下面这种枪情况。

二、有序性

字面意义就是有顺序,那么是什么有顺序呢?-- 代码

代码其实并不是我们所写的那样一五一十地执行,以C# 为例:

代码 --> IL --> Jit --> cpu 指令

代码 通过编译器的优化生成了IL

CPU也会根据自己的优化重新排列指令顺序

至少两个点会有存在调整 代码顺序/指令顺序的可能。

2.1 猜猜 Debug和Release 运行结果各是多少

public class VolatileTest
    {
        public int falg = 0;
    }
static void VolatileTest()
        {
            VolatileTest volatiler = new VolatileTest();

            new Thread(
               p =>
               {
                   Thread.Sleep(1000);
                   volatiler.falg = 255;
               }).Start();

            while (true)
            {
                if (volatiler.falg == 255)
                {
                    break;
                }
            };

            Console.WriteLine("OK");
        }

主线程一直自旋,直到子线程将值改变就退出,显示 “OK”

  • Debug 版本,执行结果:

在这里插入图片描述

Debug

  • Release 版本,执行结果:

在这里插入图片描述

Release

为什么会这样,因为我们的代码会经过编译器优化,CPU指令优化,

语句的顺序会发生改变,但是这样也是这种离奇bug产生的一种方式。

怎么避免它?

2.2 volatile

没错,依然是它,不仅仅是禁用cpu缓存,而且还能禁止指令和编译优化。

至少上面的那个例子我们可以再试试:

public class VolatileTest
    {
        public volatile int falg = 0;
    }

volatile 发布版
在这里插入图片描述

到这里应该就可以了吧,volatile 真好用,一个关键字就搞定。

正如你所想,依然没有结束。

三、原子性

我们平时经常遇到要给一段代码区域加上锁,比如这样:

lock (lockObj)
                {
                    count++;
                }

我么们为什么要加锁呢?你说为了线程同步,为什么加锁就能保证线程同步而不是其他方式?

3.1count++

说到这里,我们需要再了解一个问题:count++

我们经常写这样的代码,那么count++ 最终转换成cpu指令会是什么样子呢?

指令1: 从内存中读取 count

指令2:将 count +1

指令3:将新计算的count值,写回内存

我们将这个count++ 操作和线程切换进行结合

count++ 线程切换

这里才是真正解答了最开始为什么是 100万到200之间的随机数。

解决 原子性问题的方法有很多,比如锁
在这里插入图片描述

3.2 lock

加锁这个代码我就暂且忽略,因为lock我们并不陌生。

但是需要明白一点,lock() 是微软提供给我们的语法糖,其实最终使用的是 Monitor,并且做了异常和资源处理。
在这里插入图片描述

lock

CLR 锁原理

在这里插入图片描述

多个线程访问同一个实例下的共享变量,同时将同步块索引从 -1 改成CLR维护的同步块数组,

用完就会将实例的同步快变成-1

3.3 Monitor

上面提到了隐姓埋名的Monitor,其实我们也可以抛头露面地使用Monitor

这里也不具体细说。具体使用可以参照上面图片。

3.4 System.Threading.Interlocked

官方定义:原子性的简单操作,累加值,改变值等

区区 count++ 使用lock 有点浪费,我们使用更加轻量级的 Interlocked,

为我们的 count ++ 保驾护航。

public class ThreadTest_V3
    {
        public volatile int count = 0;
        public void Add1()
        {
            int index = 0;
            while (index++ < 1000000)//100万次
            {
                Interlocked.Add(ref count, 1);
            }
        }

        public void Add2()
        {
            int index = 0;
            while (index++ < 1000000)//100万次
            {
                Interlocked.Add(ref count, 1);
            }
        }
    }

结果不多说,依然稳稳的 200万。

3.5 System.Threading.SpinLock结构

自旋锁结构,可以这样理解。

多线程访问共享资源时,只有一个线程可以拿到锁,其他线程都在原地等待,

直到这个锁被释放,原地等待的资源又一次进行抢占,以此类推。

在具体使用 System.Threading.SpinLock结构 之前,我们根据刚刚讲过的 System.Threading.Interlocked,进行一下改造:

public struct Spin
    {
        private int m_lock;//0=unlock ,1=lock
        public void Enter()
        {
            while (System.Threading.Interlocked.Exchange(ref m_lock, 1) != 0)
            {
                //可以限制自旋次数和时间,自动断开退出
            }
        }

        public void Exit()
        {
            System.Threading.Interlocked.Exchange(ref m_lock, 0);
        }
    }
public class ThreadTest_V4
    {
        private Spin spin = new Spin();
        public volatile int count = 0;
        public void Add1()
        {
            int index = 0;
            while (index++ < 1000000)//100万次
            {
                spin.Enter();
                count++;
                spin.Exit();
            }
        }

        public void Add2()
        {
            int index = 0;
            while (index++ < 1000000)//100万次
            {
                spin.Enter();
                count++;
                spin.Exit();
            }
        }
    }

Enter() , m_lock 从0到1,就是加锁;

锁的是共享资源 count;

其他线程原地自旋等待(循环)

Exit(),m_lock 从1到0,就是解锁;

System.Threading.SpinLock 结构和以上实现思想类似。

后面的内容就简单提一下定义和应用场景,有必要的就可以单独细查。

3.6 System.Threading.SpinWait结构

提供了基于自旋等待支援。
在线程必须等待发出事件信号或满足条件时方可使用.

3.7 System.Threading.ReaderWriterLockSlim类

授予独占访问共享资源的写作,
并允许多个线程同时访问资源进行读取。

3.8 CAS

cas 核心思想:
将 count 从内存读取出来并赋值给一个局部变量,叫做 originalData;

然后这个局部变量 +1 并赋值给新值,叫做 newData;

再次从内存中将count读取出来,如果originalData ==count,

说明没有线程修改内存中count值,可以将新值存储到内存中。

反之则可以选择自旋或者其他策略。

当然还有进程之间的同步,这里就不一一展开说了。
总结一下:
并发三要素 可见性、有序性、原子性

几种锁原理和CAS操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/145958.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu中使用 vscode 连接docker开发环境

文章目录 ubuntu中使用 vscode 连接docker开发环境步骤一&#xff1a;安装 Remote Development 插件步骤二&#xff1a;连接远程环境步骤三&#xff1a;开发 问题解决参考连接 ubuntu中使用 vscode 连接docker开发环境 Remote Development 是一个 Visual Studio Code 插件&…

炒现货黄金怎么做?挖掘黄金的投资机会

黄金一直以来都是备受投资者追捧的避险资产&#xff0c;其价值和潜力是无法忽视的。而炒现货黄金作为一种快速获取收益的投资方式&#xff0c;备受关注。那么&#xff0c;如何在炒现货黄金中找到投资机会呢&#xff1f;为您详细解析&#xff0c;简单易懂&#xff0c;帮助您开启…

树的概念及结构|树的三种表示方法

前言 以前我们学的线性结构是一对一的线性关系&#xff0c;但现实中&#xff0c;还有一对多的情况要处理&#xff0c;那就是树形结构。今天我们将学习树的概念及结构、和树的三种常见表示方法。 一、树的概念及结构 1、树的概念 树是一种非线性的数据结构&#xff0c;它是由n…

MySQL--主从复制和读写分离

MySQL主从复制和读写分离相关知识 1.什么是读写分离 读写分离&#xff0c;基本的原理是让主数据库处理事务性增、改、删操作( INSERT、UPDATE、DELETE) &#xff0c;而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。 2.为什么要…

设备管理平台能做什么?企业如何做好设备维护管理工作?

在当今高度数字化的时代&#xff0c;企业运营离不开各种设备的支持。如何确保这些设备高效运转&#xff0c;降低故障率&#xff0c;成为了企业运营的关键问题。为此&#xff0c;设备管理平台应运而生&#xff0c;它借助数字化、移动互联网等技术&#xff0c;为企业提供全方位的…

劲升逻辑携手山东电子口岸及青岛港,赋能山东港口数字化建设

合作意向书签署现场 2023 年 11 月 11 日&#xff0c;中国山东——跨境贸易数字化领域的领导者劲升逻辑分别与山东省电子口岸有限公司&#xff08;简称“山东电子口岸”&#xff09;、山东港口青岛港子公司青岛港国际集装箱发展有限公司在新加坡-山东经贸理事会&#xff08;简…

【博士每天一篇文献-算法】Learning without forgetting

阅读时间&#xff1a;2023-10-29 1 介绍 年份&#xff1a;2016 作者&#xff1a;李志忠; 德里克霍伊姆&#xff0c;伊利诺伊大学 期刊&#xff1a;IEEE transactions on pattern analysis and machine intelligence 引用量&#xff1a;3353 提出一种名为"无忘记学习&quo…

在Qt设计师(Qt Designer )控件面板加入自定义控件

目录 1. 问题的提出 2. 本次开发环境说明 3. 具体实现 4. 注意的问题 5. 参考链接 1. 问题的提出 在Qt开发中&#xff0c;经常利用Qt设计师&#xff08;Qt Designer &#xff09;把界面设计好&#xff0c;将界面放到ui文件中&#xff0c;将逻辑处理放到cpp文件中&#xff…

Docker - 网络

Docker - 网络 理解Docker0 # 我们发现这个容器带来网卡&#xff0c;都是一对对的 # evth-pair 就是一对的虚拟设备接口&#xff0c;他们都是成对出现的&#xff0c;一段连着协议&#xff0c;一段彼此相连 # 正因为有了这个特性&#xff0c;evth-pair 充当一个桥梁&#xff0…

图片高清重建

图像超分辨率重建&#xff08;super resolution,SR&#xff09;是指利用计算机将一幅低分辨率图像&#xff08;low resolution,LR&#xff09;或图像序列进行处理&#xff0c;恢复出高分辨率图像&#xff08;high resolution&#xff0c;HR&#xff09;的一种图像处理技术。简单…

腾讯云4核8G服务器CVM标准型S5实例租用五年价格表

腾讯云服务器网整理五年云服务器活动 txyfwq.com/go/txy 配置可选2核4G和4核8G&#xff0c;公网带宽可选1M、3M或5M&#xff0c;系统盘为50G高性能云硬盘&#xff0c;标准型S5实例CPU采用主频2.5GHz的Intel Xeon Cascade Lake或者Intel Xeon Cooper Lake处理器&#xff0c;睿频…

【C#学习】button:只显示图片

第一步&#xff1a;设置按钮背景图片&#xff0c;并且图片随按钮大小变化 第二步&#xff1a;设置按钮使之只显示图片 button1.FlatStyle FlatStyle.Flat;//stylebutton1.ForeColor Color.Transparent;//前景button1.BackColor Color.Transparent;//去背景button1.FlatAppe…

修改Openwrt软路由的web端口

如何修改openwrt路由器的web访问端口号&#xff1f; 在OpenWrt路由器上&#xff0c;如何修改Web访问端口号&#xff0c;通常涉及到修改HTTP服务器的配置文件。默认情况下&#xff0c;OpenWrt使用的HTTP服务器是uHTTPd。 以下是修改Web访问端口号的步骤&#xff1a; 一、通过…

Meta开源支持1000多种语言的文本转语音与语音识别大语言模型

据不完全统计,地球上有超过7000多种语言,而现在的大语言模型仅仅只涉及到了主流的100多种语言。相对全球7000多种语言来讲,这仅仅只是其中的一小部分。如何让全球的人获益,把大语言模型扩展到更多的语言上,一直是大语言模型研究的重点。Meta发布了涵盖 1406 种语言的预训练…

一篇文章教会你什么是C++异常

一篇文章教会你什么是C异常 C语言传统的处理错误的方式断言检查返回值检查全局错误码设置全局错误处理函数 C异常概念基本概念注意事项 异常的使用异常的抛出和捕获异常的重新捕获异常安全异常规范 自定义异常体系C标准库的异常体系1. std::exception2. std::bad_alloc3. std::…

【算法每日一练]-图论(保姆级教程 篇1(模板篇)) #floyed算法 #dijkstra算法 #spfa算法

今天开始讲图论 目录 图的存储 算任意两点的最短路径: floyed算法&#xff1a; 算一个点到其他所有点的最短距离 dijkstra算法: spfa算法&#xff1a; 图的存储 其实&#xff1a;邻接矩阵和链式向前星都能存边的信息&#xff0c;vector只能存点的信息&#xff0c;再搭配上v[]…

2023年【汽车驾驶员(中级)】考试题库及汽车驾驶员(中级)模拟考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 汽车驾驶员&#xff08;中级&#xff09;考试题库根据新汽车驾驶员&#xff08;中级&#xff09;考试大纲要求&#xff0c;安全生产模拟考试一点通将汽车驾驶员&#xff08;中级&#xff09;模拟考试试题进行汇编&…

产品运营的场景和运营策略

一、启动屏 1&#xff0e;概念 启动屏&#xff0c;特指 APP 产品启动时即显示的界面&#xff0c;这个界面一般会停留几秒钟时间&#xff0c;在这个时间内 APP 会在后台加载服务框架、启动各种服务 SDK 、获取用户地理位置、判断有无新版本、判断用户账户状态以及其他系统级别的…

QGIS之十九矢量投影

效果 步骤 1、准备数据 2、Qgis矢量投影 Qgis工具箱中搜索“投影” 3、结果

鸿蒙HarmonyOS从零实现类微信app效果第一篇,基础界面搭建

最近鸿蒙HarmonyOS开发相关的消息非常的火&#xff0c;传言华为系手机后续将不再支持原生Android应用&#xff0c;所以对于原Android应用开发对应的Harmony版本也被一系列大厂提上了日程。作为一个名义上的移动端开发工程师&#xff08;(⊙o⊙)…&#xff0c;最近写python多过A…