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=lockpublic 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操作

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vcomp120.dll丢失怎么办?vcomp120.dll丢失的解决方法分享

vcomp120.dll丢失”。这个错误通常会导致某些应用程序无法正常运行&#xff0c;给用户带来困扰。那么&#xff0c;当我们遇到这个问题时&#xff0c;应该如何修复呢&#xff1f;下面我将为大家介绍四个修复vcomp120.dll丢失的方法。 一、使用dll修复程序修复 可以通过百度或许…

矢量绘图软件Sketch 99 for mac

Sketch是一款为用户提供设计和创建数字界面的矢量编辑工具。它主要用于UI/UX设计师、产品经理和开发人员&#xff0c;帮助他们快速设计和原型各种应用程序和网站。 Sketch具有简洁直观的界面&#xff0c;以及丰富的功能集&#xff0c;使得用户可以轻松地创建、编辑和共享精美的…

NSF服务器

目录 1.简介 1.1 NFS背景介绍 1.2 生产应用场景 2.NFS工作原理 2.1 实例图 2.2 流程 3.NFS的使用 3.1.安装 3.2.配置文件 3.3.主配置文件分析 3.4 实验 服务端&#xff1a; 客户端&#xff1a; 3.5.NFS账户映射 3.5.1.实验2 3.5.2.实验3 4.autofs自动挂载服务…

Mysql学习笔记--基础

一&#xff0c;SQL最重要的增删改命令格式 1&#xff0c;insert into 表名&#xff08;不写这个括号里面的内容就默认所有字段都要添加&#xff09; values&#xff08;&#xff09; 插入单条数据 2&#xff0c;insert into 表名 (里面是列名) values&#xff08;根据列名依次…

Java Web——前端HTML入门

目录 HTML&CSS3&JavaScript简述 1. HTML概念 2. 超文本 3. 标记语言 4. HTML基础结构 5. HTML基础词汇 6. HTML语法规则 7. VS Code 推荐使用的插件 8. 在线帮助文档 HTML&CSS3&JavaScript简述 HTML 主要用于网页主体结构的搭建&#xff0c;像一个毛坯…

【LeetCode:715. Range 模块 | 线段树】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

Django(四、路由层)

文章目录 一、路由层1.路由匹配url方法第一个是参数 的正则表达式 二、正则无名分组与有名分组无名分组有名分组 三、反向解析1.概念无名分组动态路由解析有名分组动态路由解析 四、路由分发为什么要用路由分发&#xff1f; 1.总路由分发配置名称空间 五、伪静态的概念六、虚拟…

超级干货:光纤知识总结最全的文章

你们好&#xff0c;我的网工朋友。 光纤已经是远距离有线信号传输的主要手段&#xff0c;而安装、维护光纤也是很多人网络布线的基本功。 在网络布线中&#xff0c;通常室外楼宇间幢与幢之间使用的是光缆&#xff0c;室内楼宇内部大都使用的是以太网双绞线&#xff0c;也有使用…

企业微信开发教程一:添加企微应用流程图解以及常见问题图文说明

最近在前辈的基础上新添加了一个企微应用&#xff0c;过程中遇到了一些卡点&#xff0c;这里一一通过图片标注与注释的方式记录一下&#xff0c;希望能给后来人提供一些清晰明了的帮助&#xff0c;话不多说&#xff0c;大家直接看图吧。 &#xff08;文中包括一些本项目独有的配…

linux下使用Docker Compose部署Spug实现公网远程访问

&#x1f4d1;前言 本文主要是linux下使用Docker Compose部署Spug实现公网远程访问的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &am…

游戏平台采集数据

首先&#xff0c;你需要在你的项目中添加Kotlin的网络库&#xff0c;例如OkHttp。你可以在你的build.gradle文件中添加以下依赖&#xff1a; dependencies {implementation com.squareup.okhttp3:okhttp:4.9.0 }然后&#xff0c;你可以使用以下代码来创建一个基本的网络爬虫&a…

5年测试经验之谈:2年功能测试、3年自动化测试,从入门到25k...

毕业3年了&#xff0c;学的是环境工程专业&#xff0c;毕业后零基础转行做软件测试。 已近从事测试行业8年了&#xff0c;自己也从事过2年的手工测试&#xff0c;从事期间越来越觉得如果一直在手工测试的道路上前进&#xff0c;并不会有很大的发展&#xff0c;所以通过自己的努…

ZYNQ_project:IP_ram_pll_test

例化MMCM ip核&#xff0c;产生100Mhz&#xff0c;100Mhz并相位偏移180&#xff0c;50Mhz&#xff0c;25Mhz的时钟信号。 例化单口ram&#xff0c;并编写读写控制器&#xff0c;实现32个数据的写入与读出。 模块框图&#xff1a; 代码&#xff1a; module ip_top(input …

Flutter笔记:关于Flutter中的大文件上传(上)

Flutter笔记 关于Flutter中的大文件上传&#xff08;上&#xff09; 大文件上传背景与 Flutter 端实现文件分片传输 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#…

matlab模糊控制文件m代码实现和基础理论

1、内容简介 略 15-可以交流、咨询、答疑 通过m代码来实现生成模糊文件fis文件 2、内容说明 模糊文件m代码实现和基础理论 matlab模糊控制文件m代码实现和基础理论 模糊文件、m代码和模糊基础理论 3、仿真分析 略 4、参考论文 略 链接&#xff1a;https://pan.baidu.co…

Sui学术研究奖公布,资助研究者探索人工智能、能源市场和区块链游戏

Sui基金会高兴地宣布首轮Sui学术研究奖&#xff08;SARAs&#xff09;的获奖者。SARAs计划提供资助&#xff0c;支持推动Sui区块链技术的研究。学术和研究界对我们的初次征集呈现出大量高质量的提案。 已接受的九个提案涵盖了各种主题&#xff0c;如token经济学、智能合约机制…

docker stop slow 解决

验证 NanoMQ stop slow 的问题 daemon 和非 daemon 两种方式 docker stop 都很慢 疑问是默认情况下&#xff0c;SIGTERM 会被处理。 模拟 docker 内发送 SIGTERM 信号 # The default signal for kill is TERM # pkill will send the specified signal (by defau…

开发知识点-Django

Django 1 了解简介2 Django项目结构3 url 地址 和视图函数4 路由配置5 请求及响应6 GET请求和POST请求查询字符串 7 Django设计模式及模板层8 模板层-变量和标签9 模板层-过滤器和继承继承 重写 10 url反向解析11 静态文件12 Django 应用及分布式路由创建之后 注册 一下 13 模型…

matlab直线一级倒立摆lqr控制

1、内容简介 略 16-可以交流、咨询、答疑 matlab直线一级倒立摆lqr控制 2、内容说明 倒立摆是一个开环不稳定的强非线性系统&#xff0c;其控制策略与杂技运动员顶杆平衡表演的技巧有异曲同工之处&#xff0c;目的在于使得摆杆处于临界稳定状态&#xff0c;是进行控制理论研…