CAS操作

CAS操作

概念

是Compare-And-Swap的缩写,即比较交换。是一种用于多线程编程的原子指令, 底层实现原理依赖于硬件的支持, 通常是通过处理器提供的特定指令来实现的。常用于实现无锁编程,通过他实现了原子操作,保证并发的安全性,不会造成所谓的数据不一致性的问题。

操作涉及的三个操作数

  • 内存位置(V):要操作的变量,其实就是主内存中要操作的变量
  • 预期原值(A):预期变量当前的值,其实就是线程的工作内存中变量副本
  • 新值(B):如果变量的当前值与预期的原值相同,需要写入的新值。

操作的执行逻辑

  • 首先会检车主内存位置V的当前值是否与预期原值A相等
  • 如果相等,说明自从这个值被读取以来没有被其他线程修改过,操作就会将新值B写入到这个内存位置
  • 如果不相等,说明有其他线程已经修改了这个变量,操作不会执行任何写操作

执行的示例

引用此文章

  1. 线程1从主内存中读取count变量的值到自己的工作内存中,此时count为10。(这个值我们记为:线程1期望的count值、最初读取到的count值)
  2. 线程1对自己工作内存中的count的值进行自增操作,此时count为11。(这个值我们记为:线程1更新之后的count值)
  3. 这个时候,当线程1还未执行到第三步更新count的值到主内存中的时候,线程2突然抢到了CPU执行权,它进来从主内存中读取count变量的值到自己的工作内存中,因为线程1还未更新,所以此时count仍为10。(这个值我们记为:线程2期望的count值、最初读取到的count值)
  4. 线程2对自己工作内存中的count的值进行自增操作,此时count为11。(这个值我们记为:线程2更新之后的count值)
  5. 在线程2要将自己工作内存中的count值更新到主内存之前,要执行一部重要的操作!!!就是再次读取主内存中共享变量count的值,如果此时读取到的这个值与最开始线程2期望的count值相等,那么就顺利更新count的值;如果不相等,就撤销放弃本次count++操作。
  6. 那么线程2最开始期望的count值为10,而此时从主内存中读取到的count值还是10,一样,所以就更新,将自己工作内存中的count值更新到主内存中,此时count为11。
  7. 那么线程2执行完了,此时线程1重新获得CPU执行权,它已经完成了count++的前两步了,就剩最后一步更新操作了,那么它此时同线程2一样,再次从主内存中读取共享变量count的值,一样就更新、不一样就撤销放弃操作。然而count的值已经被线程2更新过了,此时为11,而线程1最开始期望的count的值为10,两次读取到count的值不一样了!!!所以线程1就会自动撤销本次count++操作。
  8. 最终,本次线程1、2对count变量执行++操作的结果:线程1失败、线程2成功,最后count的值只增加了1次,为11

操作系统是如何保证CAS操作在回写期间组织其他线程访问的

总线锁

  • 所谓的总线索就是使用处理器提供的一份LOCK指令,当一个处理器在总线上输出此信号时,其他处理器的亲故去将被阻塞住,那么该处理器可以独占使用共享内存。
  • 在早期和一些低级硬件架构中,处理器可能会使用总线锁定来确保对内存访问的独占权。总线锁定通过阻止其他所有处理器访问内存系统来确保正在执行的 CAS 操作不会遇到并发冲突。当一个处理器在执行原子操作时,它会发出一个信号锁定总线,直到操作完成为止。这种方法简单但效率不高,因为它阻止了所有其他处理器的内存操作,影响了系统的整体性能。

缓存锁

在现代多核处理器中,通过缓存一致性协议(如 MESI 协议)来管理各个核心的缓存行状态,从而维护一致性和原子性。

  1. 获取缓存行:
    处理器将目标内存地址对应的缓存行加载到它的 L1 缓存中(如果缓存行不在缓存中或者不是最新的)。
  2. 锁定缓存行:
    在支持缓存锁的架构中,处理器会对这个缓存行加锁。这通常不是锁定总线,而是在缓存层面实现的锁。
    在支持 MESI 协议的系统中,处理器通过将缓存行置于 Exclusive(独占)状态来隐式地“锁定”这个缓存行。当缓存行处于 Exclusive 状态时,表明没有其他处理器缓存了这个地址的副本。
  3. 执行 CAS 操作:
    处理器比较缓存中的值和提供的预期值。如果相符,则更新为新值。
    这一比较和交换操作在硬件级别是原子执行的。
  4. 释放缓存行:
    在操作完成后,如果使用了缓存行锁定,则锁会被释放。
    在 MESI 协议中,如果缓存行内容被修改,则它的状态可能会转变为 Modified,并通过缓存一致性机制通知其他核心这一变化。
    在这里插入图片描述

CAS的优缺点

优点

  1. 避免锁的开销:
    CAS 提供了一种无锁的同步机制,避免了传统锁(如互斥锁和读写锁)带来的开销和复杂度。它不涉及锁管理的开销,如上下文切换、调度延迟和死锁。
  2. 系统吞吐率提高:
    在多处理器系统中,CAS 可以减少线程阻塞的时间,提高系统整体的并发能力和吞吐率。
  3. 死锁风险降低:
    由于 CAS 是无锁的,它不会导致传统锁可能引起的死锁情况。
  4. 实现简单的原子操作:
    对于简单的数据结构,如计数器、标志和指针,使用 CAS 可以简单而有效地实现它们的线程安全操作。

缺点

  1. 循环重试的开销:
    在高争用的环境中,多个线程可能反复尝试更新同一变量,导致高失败率和多次重试。这种情况下,CAS 的性能可能会下降,因为每次失败后,线程都需要重新加载变量,再尝试更新。
  2. ABA问题:
    CAS实现原子操作背后有一个假设:共享变量的当前值与当前线程提供的期望值相同,就认为这个变量没有被其他线程修改过。
    引用此文章

1.实际上这种假设不一定总是成立的。假如说有共享变量 count=100。
2.A线程将 count 修改为 200
3.B线程将 count 修改为 300
4.C线程将 count 修改为 100
5.当前线程看到的count变量的值是100,这就与最初的count变量的值是一样的,那么是否就认为count变量的值没有被其他线程更新呢?显然不是啊,它明显的被A、B两个线程更新过。
6.这就是CAS中的ABA问题,即共享变量经历了 A → B → A 的更新。

如果想要规避ABA问题,可以为共享变量引入一个修订好(或者叫时间戳),每次修改共享变量时,相应的修订号就会增加1,ABA问题的过程就转变为:[A,0] → [B,1] → [A,2] ,每次修改共享变量都会导致修订号的增加,通过修订号就可以准确的判断共享便是否被其他线程修改过。在原子类中,AtomicReference就会面临ABA问题的困扰,而AtomicStampedReference 可以很好的规避ABA问题。详细内容看下面的代码案例。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值