CAS 和 Fetch And Add 区别

1 篇文章 0 订阅

这里写自定义目录标题

CAD 和 Fetch And Add 实例

我们以AtomicInteger里面的incrementAndGet方法为例子,该方法是给变量值加1.
在jdk1.7中源码如下:

public final long incrementAndGet() {
    for (;;) {
        long current = get();
        long next = current + 1;
        if (compareAndSet(current, next))
          return next;
    }
}

在jdk1.8则是

    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

jdk1.7 用的是不断循环的CAS方法,而jdk1.8只用了一个方法搞定,其实是运用了cpu x86的 LOCK XADD 指令。后者的性能优于前者。两者的对比,oracle官网的这篇文章介绍的非常详细。以下是我对这篇文章的翻译。
https://blogs.oracle.com/dave/atomic-fetch-and-add-vs-compare-and-swap

译文

在许多情况下,原子Fetch and add指令可能产生比传统Load更好的性能;Φ; CAS循环,其中CAS是原子比较和交换指令。 x86架构提供了了LOCK:XADD指令,我将在下面的讨论中使用它。 (如果您不需要关系到原始值,也可以使用LOCK:INC或LOCK:ADD而不是LOCK:XADD)。

  1. CAS是“乐观锁”并且可能失败,而XADD则不承认。使用XADD时,没有明确的远程干扰窗口,因此不需要重试循环。可以说,XADD具有更好的进度属性,因为底层XADD没有隐式循环判断,但即使在这种情况下,窗口也会比使用Load;Φ; CAS更窄。

  2. 如果使用典型的Load;Φ; CAS,这是基于snoop的缓存一致性,则Load可能会导致读取共享总线事务使底层缓存行进入S(Share)或E(Exclude)状态。遵守缓存一致性协议的CAS可以引起另一总线事务以线路升级到M状态。因此,在最坏的情况下,CAS可能会导致两个总线事务.但XADD通常会将线路直接驱动到M(Modified)状态,只需要一个总线事务。当然,可以通过推测值方法,在没有任何先前加载的情况下,直接进行CAS的操作。 (我经常在本机HotSpot代码中使用它)。此外,复杂的CPU可以执行相关推测并积极地将目标线设置为M(Modified)状态。最后,在某些情况下,您可以在加载之前执行prefetch-for-write(PREFETCHW)指令,以避免升级事务。但是这种方法必须谨慎应用,因为在某些情况下它可能弊大于利。鉴于此,XADD在可用情况下具有优势。

  3. 让我们假设您尝试使用通常的Load; INC; CAS循环来增加变量。当CAS失败的频率很大时候,可以发现退出循环的分支指令(通常在无争用或轻度争用下)开始预测我们停留在循环中的故障路径。因此,当CAS最终成功时,在尝试退出循环时会导致分支错误预测。对于具有深度管道和大量无序处理信息机制的处理器而言,这可能会非常痛苦。通常情况下,执行一段代码,并不需要长时间停顿。相关地,当CAS开始频繁失败,分支指令开始预测控制保持在循环中,循环运行得越快,就更快地循环到CAS。通常,我们希望循环执行中有一些回退。在轻负载且不经常发生故障的情况下,再次循环的错误预测可能是一种有用的隐含回退。但是在更高的负荷下,我们失去了因为错误错误预测带来的隐含回退的好处。由于XADD没有循环,自然也没有以上的问题。

尽管CAS具有更高(无限)的consensus number,但有时候fetch-and-add–其consensus number仅为2 - 是更适合的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: __sync_fetch_and_add是GCC内置函数之一,用于实现原子操作。它的作用是将指定内存地址的值加上一个指定的值,并返回原来的值。在多线程编程中,使用__sync_fetch_and_add可以避免竞争条件,确保数据的一致性和正确性。 ### 回答2: __sync_fetch_and_add是GCC的内建函数之一,用于实现原子操作的一种方式,主要是用于多线程编程中对于共享变量进行加法操作,且保证操作的原子性。 它接受两个参数,第一个参数为指向需要进行操作的变量的指针,第二个参数为变量需要增加的值。 __sync_fetch_and_add函数的过程是,首先将需要进行操作的变量的值读取到寄存器中,然后对寄存器中的值进行加法操作,接下来将结果写回到原来的变量中,并返回加法操作前的值。 这个过程具有原子性,也就是在这个过程中其他线程不能干扰它的操作。 具体来说,如果有两个线程同时调用__sync_fetch_and_add函数对同一个变量进行加法操作,那么操作的过程是这样的: 1. 线程1把变量的值读取到寄存器1中。 2. 线程2也把变量的值读取到寄存器2中。 3. 线程1对寄存器1中的值进行加法操作,结果存储到寄存器1中。 4. 线程2也对寄存器2中的值进行加法操作,结果存储到寄存器2中。 5. 线程1将结果写回到变量中,变量的值为加法操作前的值加上需要增加的值,即加法操作前的值+增加的值,存储到变量中。 6. 线程2也将结果写回到变量中,由于此时变量的值已经发生了改变,所以线程2写回的值是加法操作前的值加上需要增加的值再加上线程1进行加法操作后增加的值,即加法操作前的值+增加的值+增加的值,存储到变量中。 因此,__sync_fetch_and_add函数保证了多线程编程中对于共享变量的加法操作的原子性,能够使程序正常运行而不出现问题。 ### 回答3: __sync_fetch_and_add是GCC内建函数库中的一个函数,用于对变量在原子级别上执行加法运算操作。在多线程并发的情况下,当多个线程访问同一个变量时,如果不使用原子操作,可能会导致数据不一致的问题。而__sync_fetch_and_add函数能够保证在并发操作的情况下,仅有一个线程能够修改变量的值,确保了数据的一致性。 __sync_fetch_and_add中的sync表示该函数是同步函数,fetch表示该函数用于从内存中读取数据更新到变量中,and表示该函数是原子操作的一种。在加法操作过程中,当多个线程同时访问变量时,函数先获取原有的变量值,然后对变量值进行加法运算,并将运算结果更新到内存中。当多个线程都执行完加法操作后,变量的值就是所有执行过加法操作的线程的和。 __sync_fetch_and_add函数的语法如下: int __sync_fetch_and_add(int *ptr, int value); 其中,*ptr表示变量的地址,value表示要加的值。该函数返回原有变量的值。 需要注意的是,在使用__sync_fetch_and_add函数时,要确保变量类型是合法的,例如,如果变量类型是C++中的类类型,则不能使用__sync_fetch_and_add函数进行操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值