2016 - 2024

感恩一路有你

compare方法的原理 在CAS中,如何保证两个线程的compare结果不是同时为true?

浏览量:3074 时间:2023-04-03 11:08:36 作者:采采

在CAS中,如何保证两个线程的compare结果不是同时为true?

比如一个简单的 int 变量,在不对这个变量加锁的情况下仅使用 CompareAndSwap 是如何实现原子操作的?

因为可能 2 个线程同时拿到了这个变量,那么如果这时 2 个线程几乎是同时 compare 的时候可能就是同时为 true。

那么底层是否其实是存在类似锁的实现呢?

举个 Go 中协程的例子:

package main

import (

fmt

sync

sync/atomic

)

var counter int32 0

func main() {

n : 100

wg : new(sync.WaitGroup)

(n)

for i:0 i ltn i {

go inc(i, wg)

}

wg.Wait()

(counter)

}

func inc(i int, wg *sync.WaitGroup) {

d: 0

for {

old : counter

if (ampcount:

cas串级操作原理?

1、什么是CAS?

CAS:Compare and Swap,即比较再交换。

jdk5增加了并发包*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。

2、CAS算法理解

对CAS的理解,CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

CAS比较与交换的伪代码可以表示为:

do{

备份旧数据;

基于旧数据构造新数据;

}while(!CAS( 内存地址,备份的旧数据,新数据 ))

注:t1,t2线程是同时更新同一变量56的值

因为t1和t2线程都同时去访问同一变量56,所以他们会把主内存的值完全拷贝一份到自己的工作内存空间,所以t1和t2线程的预期值都为56。

假设t1在与t2线程竞争中线程t1可以更新变量的值,而所有其他线程都失败。失败的线程不会被挂起,但会被告知本次比赛失败,可以再试一次。T1线程将变量值更新为57,然后写入内存。此时,对于t2,内存值变为57,与期望值56不一致,操作失败(要改变的值不再是原来的值)。

(上图通俗的解释就是CPU更新了一个值,但是如果你要改变的值不再是原来的值,操作就会失败,因为很明显,其他操作已经先改变了这个值。)

意思是两者比较时,如果相等,证明共享的数据没有被修改,被新的值代替,然后继续向下运行;如果不相等,说明共享的数据已经被修改,放弃已经做过的操作,然后重新执行刚才的操作。很容易看出,CAS操作是基于共享数据不会被修改的假设,采用类似于数据库的提交-重试模式。当同步的机会很少时,这种假设可以带来很大的性能提升。

开销

前面说过,CAS(比较和交换)是CPU的指令级操作,只有一个原子操作,所以速度很快。而且CAS避免了请求操作系统决定锁的问题,直接在CPU内部完成,不打扰操作系统。但是CAS没有费用?不要!那里 这是缓存未命中的情况。这个问题比较复杂。首先,我们需要知道CPU的硬件架构:

上图可以看到一个8核CPU的电脑系统。cache(CPU有一个cache(CPU的内部缓存,寄存器),管芯中有一个互联模块,让管芯中的两个内核可以互相通信。图中央的系统互连模块允许四个芯片相互通信,并将芯片与主存储器连接。数据在系统中以 单位传输高速缓存线 ",对应内存中2的幂的字节块,通常在32到256字节之间。当CPU将一个变量从内存读入其寄存器时,它必须首先将包含该变量的缓存行读入CPU缓存。类似地,当CPU将寄存器中的值存储到内存中时,它不仅必须将包含该值的缓存行读取到CPU缓存中,还必须确保没有其他CPU拥有该缓存行的副本。

例如,如果CPU0对某个变量执行比较和交换操作,而该变量的缓存行在CPU7的缓存中,则会发生以下简化的事件序列:

CPU0检查了本地缓存,没有找到缓存行。

请求转发到CPU0和CPU1的互联模块,检查CPU1的本地缓存,没有发现缓存线。

请求被转发到系统互连模块,并且检查了其他三个管芯,并且知道缓存线受到CPU6和CPU的保护。7被保持在模具中。

请求被转发到CPU6和CPU7的互连模块,检查这两个CPU的缓存,找到CPU7的缓存中的缓存行。

CPU7将高速缓存行发送到附属的互连模块,并刷新其自己的高速缓存中的高速缓存行。

CPU6和CPU7的互连模块将缓存线发送到系统互连模块。

系统互连模块将缓存线发送给CPU0和CPU1的互连模块。

CPU0和CPU1的互连模块将缓存线发送到CPU0的缓存。

CPU0现在可以对缓存中的变量执行CAS操作。

以上是刷新不同CPU缓存的开销。最好的CAS操作消耗大约40纳秒,超过60个时钟周期。 "最佳案例 "这里的意思是对一个变量执行CAS操作的CPU正好是最后一个操作该变量的CPU,所以对应的缓存行已经在该CPU的缓存中了。类似地,最好的锁操作(a "往返对 "包括获取锁然后释放锁)花费超过60纳秒和超过100个时钟周期。 "最佳案例 "这里意味着用于表示锁的数据结构已经在获取和释放锁的CPU的缓存中。因为对并行编程的深刻理解,锁操作比CAS操作更费时。

在为锁操作的数据结构中需要两个原子操作。高速缓存未命中消耗大约140纳秒,超过200个时钟周期。CAS操作需要在存储新值时查询变量的旧值,大约需要300纳秒,超过500个时钟周期。想想这个。在执行CAS操作时,CPU可以执行500条通用指令。这显示了细粒度锁的局限性。

CAS CPU 变量

版权声明:本文内容由互联网用户自发贡献,本站不承担相关法律责任.如有侵权/违法内容,本站将立刻删除。