CAS到底是怎么回事

  • 为什么需要CAS
  • 如何实现CAS
  • 关于CAS和ABA
  • 关于应用层的锁和CPU的锁的关系
  • 参考

为什么需要CAS

CAS全称为Compare And Set(比较并交换)

对于现代操作系统而言,一般都是多核机器,会存在好几个cpu,而每块cpu都单独有一套缓存和mmu等组件。

多核CPU的好处是能够实现线程级别的并发操作,极大提高程序执行效率,但是这也会导致并发问题的产生,如下面这个场景:

关于操作系统进程,线程和时间片关系,可以看这篇文章了解一下: Linux系统中 进程 、线程 、时间片的关系?


两个cpu上并行运行着一个java进程中的两个线程,这两个线程几乎同时从主存中读取变量i的值,放入高速缓存中,然后交给cpu进行自增操作,操作完后再写回高速缓存,最后再写回主存。

如果cpu要修改的内存地址在高速缓存中存在,那么称为写命中,这种情况下,有两种策略来将高速缓存中修改后的数据写回主存,分别为:

  • 全写法: 当CPU对Cache写命中时,必须把数据同时写入Cache和主存,一般使用一个缓存区来存放要写回主存的数据,然后慢慢写回主存,此时cpu可以干其他事情,这个过程由硬件实现。
  • 写回法: 当cpu对cache写命中时,只修改cache的内容,而不立即写入主存,只有当此块被换出时才会写回主存。

上面多个cpu同时并发读写同一个内存单元,显然会导致结果与预期不符,其问题本质在于对于内存单元的读写操作是非原子性的,如何保证对内存单元读写操作的原子性,这就是CAS需要完成的使命。

原子性意味着不能允许多个cpu同时读写同一块内存单元


如何实现CAS

要避免多个cpu同时读写同一块内存单元,对于从事上层应用开发的程序员而言,最先想到的就是加锁,但是cpu层面的加锁如何实现呢?

  • 更加接近机器层面的语言就是汇编语言了,我们只需要通过x86汇编语言中提供的lock前缀的指令就可以完成上述加锁功能
  • x86汇编中,如果对一个指令加“lock”前缀,会发生什么 ?

对于早期的CPU,总是采用的是锁总线的方式。具体方法是,一旦遇到了Lock指令,就由仲裁器选择一个核心独占总线。其余的CPU核心不能再通过总线与内存通讯。从而达到“原子性”的目的。

这种方式的确能解决问题,但是非常不高效。为了个原子性结果搞得其他CPU都不能干活了。因此从Intel P6 CPU开始就做了一个优化,改用Ringbus + MESI协议,也就是文档里说的cache conherence机制。这种技术被Intel称为“Cache Locking”。

根据文档原文:如果是P6后的CPU,并且数据已经被CPU缓存了,并且是要写回到主存的,则可以用cache locking处理问题。否则还是得锁总线。因此,lock到底用锁总线,还是用cache locking,完全是看当时的情况。当然能用后者的就肯定用后者。

MESI大致的意思是:若干个CPU核心通过ringbus连到一起。每个核心都维护自己的Cache的状态。如果对于同一份内存数据在多个核里都有cache,则状态都为S(shared)。一旦有一核心改了这个数据(状态变成了M),其他核心就能瞬间通过ringbus感知到这个修改,从而把自己的cache状态变成I(Invalid),并且从标记为M的cache中读过来。同时,这个数据会被原子的写回到主存。最终,cache的状态又会变为S。

这相当于给cache本身单独做了一套总线(要不怎么叫ring bus),避免了真的锁总线。

回到CAS。

我们一般说的CAS在x86的大概写法是

lock cmpxchg a, b, c

对于一致性来讲,“lock”前缀是起关键作用的指令。cas的实现用了lock cmpxchg指令。cmpxchg指令涉及一次内存读和一次内存写,需要lock前缀保证中间不会有其它cpu写这段内存。

CAS的特性使得他称为实现任何高层“锁”的必要的构建。几乎所有的“锁”,如Mutex,ReentrantLock等都得用CAS让线程先原子性的抢到一个东西(比如一个队列的头部),然后才能维护其他锁相关的数据。并且很有意思的是,如果一个竞争算法只用到了CAS,却没有让线程“等待”,就会被称为“无锁算法”。CPU会不会有点小郁闷……


关于CAS和ABA

实际上CAS和cmpxchg压根就没处理过ABA问题。严格来说CAS就不会有ABA的问题,它只是一个简单的,原子的"比较-设置值"的指令而已。

会出ABA问题的是这种CAS的用法:

var curVal = getValue();
var newVal = modify(curVal);
compareAndSwap(getValueAddr(), curVal, newVal); // 这里是CAS

即这个代码的第一句和第三句可能看到的curVal是一样的,但是有可能造这个curVal在另一个线程ABA了。

如果真的需要解决ABA问题,需要上层代码来处理,比如

  • 把value和version放到一起形成一个变量的值(比如 "62@v1“),然后对这个变量的值做CAS。这种比较适合值本身比较简单的场景。
  • 把value和version包一个对象,然后对对象的引用做CAS(Java的AtomicStampedReference就是这么干的)。

现实工程当中,绝大部分情况下,都不需要考虑ABA问题。


关于应用层的锁和CPU的锁的关系

CPU锁和应用层的锁要解决的问题不一样。

CPU锁主要解决的是多个核心并发访问/修改同一块内存的问题。所以有锁总线和MESI协议来做。对于上层主要的抽象就是CAS。主要的招数就是用CAS+循环来抢东西。如果抢不到就只能:

  • 继续循环下去玩命抢(这时会空耗CPU)
  • 不抢了,回复给上层代码“抢不到”。

应用层的锁存在了“进程/线程“的概念(下文统一都说进程)。解决的是多个进程并发访问同一块内存的问题。比起CPU的层级来说,应用层的锁可以多一个招数,叫做“让当前进程不可调度“。这个是OS提供的支持。

因此在应用层的层次上你可以定义一个高级的“锁”,大概执行这样一个抢锁流程

  1. 尝试用CAS抢到锁
  2. 如果抢不到,则回到1重试
  3. 如果抢了几十次都还抢不到,就把当前进程(的信息)尝试挂到一个等待队列上(当然挂的过程还是要CAS)
  4. 把当前进程设定为不可调度,这样OS就不会把当前进程调度给CPU执行。(这种情况因为需要做一次系统调用,所以有比较大的损耗,一般被称为“重量级锁”)

而当某个进程释放锁时,他就可以做释放锁的流程

  1. 找到释放锁的那个等待队列
  2. 把等待队列里第一个等待的进程信息取出来,并且告诉OS,这个进程程可以执行了(这里也要做一次系统调用)
  3. 这个被复活了的进程一般需要在做一次循环尝试抢锁,然后就回到了上面的抢锁流程。

简单来说,CAS是应用层锁的building block。


参考

cas做了锁了总线或缓存行还是volatile做了锁总线或缓存行?

CAS到底是怎么回事相关推荐

  1. 设置文件权限位时我们一般忽略了suid/guid的存在,现在看看它们到底是怎么回事

    设置文件权限位时我们一般忽略了suid/guid的存在,现在看看它们到底是怎么回事. suid/guid是什么? suid意味着如果A用户对属于他自己的shell脚本文件设置了这种权限,那么其他用户在 ...

  2. c语言列车调度 栈,这个用栈调度火车的程序到底是怎么回事?

    这个用栈调度火车的程序到底是怎么回事? 下面是一个用栈来实现火车调度的程序,其中,in代表初始的车列顺序,transter代表用来调度的火车轨,out代表调度后的火车轨,程序功能如下,如果in中车列顺 ...

  3. dakai微信小程序 ios_【iOS】微信小程序打开APP到底是怎么回事?

    前言 从苹果官方来看,小程序新增了两个功能: 1. 支持打开移动应用 2. 标题栏区域开放自定义 针对第二个功能,就是开发者可以自定义小程序菜单栏的颜色风格,根据需求,对小程序菜单外的标题区域进行自定 ...

  4. 大学计算机怎么开机,电脑开机卡logo,大学生没电脑玩急坏了,这到底是怎么回事?...

    电脑常见的故障有蓝屏白字.黑屏白字.红屏白字.卡logo.打不开程序.自动关机重启,这些故障有的是硬件问题引起,有的是软件问题引起,对于硬件引起的故障,我们首先要判断是接触不良引起的故障,还是因为损坏 ...

  5. 【定位不准的烦心事系列】第2篇:卫星信号弱到底是咋回事

    对于每个使用手机导航App的用户来说,最怕听到的就是"卫星导航信号弱"这个提示,因为这意味着定位不准了,用户可能无法获得准确的指引.那么信号弱到底是咋回事呢?明明没有遮挡,咋就收不 ...

  6. 【售票员内部资料】告诉你所谓的“提前10天售火车票”到底是怎么回事

    [售票员内部资料]告诉你所谓的"提前10天售火车票"到底是怎么回事    我们可能只知道"提前10天售火车票"这个信息,也就是说,火车票只在开车前10天放一次票 ...

  7. 留几手:互联网创业到底是咋回事(说得真经典,创业者不创业的都值得一看)

    不知道从啥时候开始,兴起了一股风潮,大学生一毕业基本都不找工作了,现在都必须得创业.几个朋友一聚会,聊天,你要说你在哪哪上班打工,人家就觉得你没上进心.没个"XX创始人".&quo ...

  8. 人工智能之无人驾驶技术到底是怎么回事

    之前AlphaGo击败韩国名将李世石的新闻让更多的人开始关注人工智能,特别是后来AlphaGo Zero,只训练的40天又把AlphaGo远远超越.着实让人不得不感叹道:哎呀卧槽!好牛逼!!要知道,之 ...

  9. 习惯了收听虾米酷狗网易云音乐的你,好歹知道一下音乐推荐到底是咋回事吧

    翻译 | reason_ W编辑 | suiling 营长按: 不好意思,被标题党了吧 其实,我们全篇讲的是坐音乐推荐的始祖Spotify的音乐推荐系统. 搞懂了这货的算法,还有啥是你不知道的. 不说 ...

最新文章

  1. 创建Silverlight自定义启动画面
  2. Alpha冲刺 - (5/10)
  3. 语言趣味编程100例无水印_趣味c语言编程100例(三)
  4. 滤波、漫水填充、图像金字塔、图像缩放、阈值化
  5. 在iview项目中添加echarts3
  6. gram矩阵_推荐算法三视角:矩阵,图,时间线
  7. CDI services--Decorators(装饰器)
  8. 用聚合数据API快速写出小程序
  9. PHP博客导入导出,Thinkphp5.0导入导出详解
  10. 待起飞的の集训8.5
  11. 屏幕共享软件开发_工作生活离不开软件,你知道什么是免费软件与共享软件吗?...
  12. netty使用(5)client_server一发一回阐释ByteBuffer的使用
  13. 使用oss对象存储的ossutil工具
  14. 牛逼!SpringBoot+Vue企业级支付系统!附源码!
  15. 简要罗列通过Allegro绘制的PCB封装步骤
  16. ZigBee模块——从新冠、癌症防治看物联网智慧医疗的应用
  17. 【OMNeT++】ALOHA协议仿真中的channelUtilization
  18. 超详细Gulp打包seajs模块压缩合并,绝对对你项目有帮助 ~
  19. mysql innodb_large_prefix
  20. 数据库 连接(自然连接,内连接,外连接)

热门文章

  1. WINDOWS10使用LPR方式连接网络打印机
  2. ipfs 云服务器_图们时空云,ipfs服务器
  3. python变量名区分大小写_Python变量名区分大小写,所以student和Student不是同一个变量。...
  4. Failed to download https://chrome-infra-packages.appspot.com/dl/flutter/web/canvaskit_bundle 超时 解决办法
  5. COM注册什么时候需要注册APPID
  6. AI智能语音机器人源码供应批发采购
  7. Spring源码版本命名规则
  8. 光纤网卡PCI-E和HBA网卡的区别?
  9. ZOJ 3898 Stean (求旋转体体积与面积)
  10. spring核心编程思想—beanfacotry和applicationcontext区别