【C# 线程】并发编程的基石——CAS机制
其实Java并发框架的基石一共有两块,一块是本文介绍的CAS,另一块就是AQS,后续也会写博客介绍。
什么是CAS机制
CAS机制是一种数据更新的方式。在具体讲什么是CAS机制之前,我们先来聊下在多线程环境下,对共享变量进行数据更新的两种模式:悲观锁模式和乐观锁模式。
悲观锁更新的方式认为:在更新数据的时候大概率会有其他线程去争夺共享资源,所以悲观锁的做法是:第一个获取资源的线程会将资源锁定起来,其他没争夺到资源的线程只能进入阻塞队列,等第一个获取资源的线程释放锁之后,这些线程才能有机会重新争夺资源。synchronized就是java中悲观锁的典型实现,synchronized使用起来非常简单方便,但是会使没争抢到资源的线程进入阻塞状态,线程在阻塞状态和Runnable状态之间切换效率较低(比较慢)。比如你的更新操作其实是非常快的,这种情况下你还用synchronized将其他线程都锁住了,线程从Blocked状态切换回Runnable华的时间可能比你的更新操作的时间还要长。
乐观锁更新方式认为:在更新数据的时候其他线程争抢这个共享变量的概率非常小,所以更新数据的时候不会对共享数据加锁。但是在正式更新数据之前会检查数据是否被其他线程改变过,如果未被其他线程改变过就将共享变量更新成最新值,如果发现共享变量已经被其他线程更新过了,就重试,直到成功为止。CAS机制就是乐观锁的典型实现。
CAS,是Compare and Swap的简称,在这个机制中有三个核心的参数:
- 主内存中存放的共享变量的值:V(一般情况下这个V是内存的地址值,通过这个地址可以获得内存中的值)
- 工作内存中共享变量的副本值,也叫预期值:A
- 需要将共享变量更新到的最新值:B

如上图中,主存中保存V值,线程中要使用V值要先从主存中读取V值到线程的工作内存A中,然后计算后变成B值,最后再把B值写回到内存V值中。多个线程共用V值都是如此操作。CAS的核心是在将B值写入到V之前要比较A值和V值是否相同,如果不相同证明此时V值已经被其他线程改变,重新将V值赋给A,并重新计算得到B,如果相同,则将B值赋给V。
值得注意的是CAS机制中的这步步骤是原子性的(从指令层面提供的原子操作),所以CAS机制可以解决多线程并发编程对共享变量读写的原子性问题。
ABA问题
所谓ABA问题, 就是比较并交换的循环,存在一个时间差,而这个时间差可能带来意想不到的问题。
比如有两个线程A、B:
一开始都从主内存中拷贝了原值为3;
A线程执行到var5=this.getIntVolatile,即var5=3。此时A线程挂起;
B修改原值为4,B线程执行完毕;
然后B觉得修改错了,然后再重新把值修改为3;
A线程被唤醒,执行this.CompareTxchange( )方法,发现这个时候主内存的值等于快照值3,(但是却不知道B曾经修改过),修改成功。
尽管线程A CAS操作成功,但不代表就没有问题。有的需求,比如CAS,只注重头和尾,只要首尾一致就接受。但是有的需求,还看重过程,中间不能发生任何修改。这就引出了原子引用。
原子引用
Int32对整数进行原子操作,如果是一个普通的对象呢?可以用 Interlocked.CompareExchange<T>(T, T, T)泛型来包装这个普通类,使其操作原子化。
C# 对CAS的ABA问题的解决方案
C#,通过Interlocked方法实现。CAS在.NET中的实现类是Interlocked,内部提供很多原子操作的方法,最终都是调用Interlocked.CompareExchange()
Windows,通过Windows API实现了InterlockedCompareExchangeXYZ系列函数。
CAS机制优缺点
CAS的适用场景
读多写少:如果有大量的写操作,CPU开销可能会过大,因为冲突失败后会不断重试(自旋),这个过程中会消耗CPU
单个变量原子操作:CAS机制所保证的只是一个变量的原子操作。
CAS总结
任何技术都不是完美的,当然,CAS也有他的缺点:
CAS实际上是一种自旋锁,
一直循环,开销比较大。
只能保证一个变量的原子操作,多个变量依然要加锁。
引出了ABA问题(C# Interlocked.CompareExchange()方法 可解决)。
而他的使用场景适合在一些并发量不高、线程竞争较少的情况,加锁太重。但是一旦线程冲突严重的情况下,循环时间太长,为给CPU带来很大的开销。
CAS机制的案例:
下面的基本示例展示了无锁堆栈中的 SpinWait 结构。 如果需要高性能的线程安全堆栈,请考虑使用 System.Collections.Concurrent.ConcurrentStack<T>。
详解:启用3个线程给自定义堆栈LockFreeStack<T>的 字段reeStac 添加数据(0-20)。用到cas 技术保证了线程的同步。
LockFreeStack<int> reeStac = new(); for (int i = 1; i <=3; i++)
{
Thread se = new Thread(test);
se.Start();
} void test(){ for (int i = 0; i < 20; i++)
{
reeStac.Push(i); } } public class LockFreeStack<T>
{
private volatile Node m_head; private class Node { public Node Next; public T Value; } public void Push(T item)
{
var spin = new SpinWait();
Node node = new Node { Value = item }, head ;
while (true)
{
head = m_head;
node.Next = head;
Console.WriteLine("Processor:{0},Thread{1},priority:{2} count:{3} ", Thread.GetCurrentProcessorId(), Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Priority,item );
Node dd = Interlocked.CompareExchange(ref m_head, node, head);//如果相等 就把node赋值给m_head,返回值都是原来的m_head。
if (dd == head) break;//判断是否赋值成功。成功就跳出死循环。
spin.SpinOnce();
Console.WriteLine("Processor:{0},Thread{1},priority:{2} spin.SpinOnce()", Thread.GetCurrentProcessorId(), Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Priority);
}
} public bool TryPop(out T result)
{
result = default(T);
var spin = new SpinWait(); Node head;
while (true)
{
head = m_head;
if (head == null) return false;
if (Interlocked.CompareExchange(ref m_head, head.Next, head) == head)
{
result = head.Value;
return true;
}
spin.SpinOnce();
}
}
}
【C# 线程】并发编程的基石——CAS机制的更多相关文章
- 并发编程的基石——CAS机制
本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. 并发编程系列博客传送门 Java中提供了很多原子操作类来保证共享变量操作的原子性 ...
- Java并发编程:Concurrent锁机制解析
Java并发编程:Concurrent锁机制解析 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: # ...
- 并发编程的基石——AQS类
本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. 并发编程系列博客传送门 本文参考了[Java多线程进阶(六)-- J.U.C之l ...
- 超强图文|并发编程【等待/通知机制】就是这个feel~
你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it well enough ...
- Java并发编程(06):Lock机制下API用法详解
本文源码:GitHub·点这里 || GitEE·点这里 一.Lock体系结构 1.基础接口简介 Lock加锁相关结构中涉及两个使用广泛的基础API:ReentrantLock类和Condition接 ...
- Go并发编程之美-CAS操作
摘要: 一.前言 go语言类似Java JUC包也提供了一些列用于多线程之间进行同步的措施,比如低级的同步措施有 锁.CAS.原子变量操作类.相比Java来说go提供了独特的基于通道的同步措施.本节我 ...
- Java并发编程基础-ReentrantLock的机制
同步锁: 我们知道,锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源,在Lock接口出现之前,Java应用程序只能依靠synchronized关键字来实现同步锁 ...
- 简论数据库乐观悲观锁与并发编程中的CAS
为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/5783205. ...
- 【并发编程】Binder运行机制的流程图
Binder工作在Linux层面,属于一个驱动,只是这个驱动不需要硬件,或者说其操作的硬件是基于一小段内存.从线程的角度来讲,Binder驱动代码运行在内核态,客户端程序调用Binder是通过系统调用 ...
随机推荐
- 豆瓣爬虫——通过json接口获取数据
最近在复习resqusts 爬虫模块,就重新写了一个豆瓣爬虫,这个网页从HTML 源码上来看是没有任何我想要的信息的,如下图所示: 这是网页视图,我在源码中查找影片信息,没有任何信息,如图: 由此我判 ...
- elasticsearch启动流程
本文基于ES2.3.2来描述.通过结合源码梳理出ES实例的启动过程. elasticsearch的启动过程是根据配置和环境组装需要的模块并启动的过程.这一过程就是通过guice注入各个功能模块并启动这 ...
- Python SQL execute加参数的原理
在Python中,当用pymysql库,或者MySQLdb库进行数据库查询时,为了防止sql注入,可以在execute的时候,把参数单独带进去,例如: def execute_v1(): config ...
- gin源码解读1-net/http的大概流程
gin框架预览 router.Run()的源码: func (engine *Engine) Run(addr ...string) (err error) { defer func() { debu ...
- golang中的go get命令
### 下载指定版本 go get k8s.io/klog@v1.0.0 go get 命令可以借助代码管理工具通过远程拉取或更新代码包及其依赖包,并自动完成编译和安装. 这个命令在内部实际上分成了两 ...
- Device or resource busy
格式化磁盘显示忙碌,如何解决呢? [root@jp33e503-11-8 ~]# mkfs.xfs /dev/sdc mkfs.xfs: cannot open /dev/sdc: Device or ...
- Transformer可解释性:注意力机制注意到了什么?
原创作者 | FLPPED 论文: Self-Attention Attribution: Interpreting Information Interactions Inside Transform ...
- onerror事件捕获网页中的错误
转载请注明来源:https://www.cnblogs.com/hookjc/ <html><head><script type="text/javascrip ...
- 样式操作案例5-改变box的大小和位置
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- bootstrap移动 pc 响应轮播
PC端效果 width100% 移动端 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta ...