热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

CAS到底是怎么回事

CAS到底是怎么回事为什么需要CAS如何实现CAS关于CAS和ABA关于应用层的锁和CPU的锁的关系参考为什么需要CASCAS全称为CompareAndSet(比较并交换)对于现代

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做了锁总线或缓存行?


推荐阅读
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了源码分析--ConcurrentHashMap与HashTable(JDK1.8)相关的知识,希望对你有一定的参考价值。  Concu ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • MySQL中的MVVC多版本并发控制机制的应用及实现
    本文介绍了MySQL中MVCC的应用及实现机制。MVCC是一种提高并发性能的技术,通过对事务内读取的内存进行处理,避免写操作堵塞读操作的并发问题。与其他数据库系统的MVCC实现机制不尽相同,MySQL的MVCC是在undolog中实现的。通过undolog可以找回数据的历史版本,提供给用户读取或在回滚时覆盖数据页上的数据。MySQL的大多数事务型存储引擎都实现了MVCC,但各自的实现机制有所不同。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
  • 1引言在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着JavaSE1.6对Synchronized进行 ... [详细]
author-avatar
苦咖啡青柠檬
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有