CAS编程的优缺点

时间:2013-06-03 14:55:43

标签: java c++ lock-free compare-and-swap lockless

有谁能总结一下比较和交换编程的优缺点? (例如多核CPU性能)

以下是Java中的示例:

/**
 * Atomically increments by one the current value.
 *
 * @return the updated value
 */
public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

===编辑===

请在单/核CPU中专门讨论这个问题。

2 个答案:

答案 0 :(得分:3)

优点:没有锁,因此没有死锁,通常具有更好的可扩展性

缺点:饥饿的风险(除非算法也是等待的,但通常情况并非如此)

编辑:无等待算法在丢失CAS竞赛时执行某些操作。而不是繁忙/启动。

答案 1 :(得分:0)

如果没有内置实现所需原子操作的语言,则仅在源代码中编写CAS重试循环。硬件(尤其是x86)通常可以做得更好。

Java的AtomicInteger具有getAndIncrement()incrementAndGet()方法(至少从Java 7开始),这使得JVM可以轻松地将其JIT到asm中,而且比实际的CAS重试循环更有效。就像C ++ 11的std::atomic::fetch_add()。另请参见Practical uses for AtomicInteger

在x86上,您希望JVM利用x86的硬件支持来执行此操作。如果您使用直接映射到它的函数而不是CAS,则更有可能发生这种情况-retry循环,优化器必须努力才能将其优化为非循环实现。

(当多个CPU内核争用同一条缓存行时,将执行lock操作的硬件总线/缓存仲裁;一次只有一个线程实际上可以拥有该缓存行并进行递增。您可以争论即使{steps}是时钟周期而不是CPU指令,它也是wait-free:在任何给定系统上,即使与所有其他系统一样,lock操作要等待多长时间也可能有较低的上限核心敲击同一条缓存行。)

; possible x86 implementation of incrementAndGet() for a 32-bit integer
; which you'd hopefully get (after inlining and so on)

mov    eax,1
lock   xadd [mem], eax       ; atomically do [mem]+=eax, and put the old value in eax
inc    eax                   ; old_value += 1 to get the new value
; result in EAX

不需要循环。

在LL / SC机器(大多数非x86机器,如ARM,PowerPC,MIPS)上,会有一个重试循环,但它并不完全是CAS。 LL / SC机器上的CAS重试循环会产生额外的开销。这非常小,但是让JVM直接看到您想要的原子操作绝对更好。有关CAS与LL / SC的更多讨论,请参见Atomically clearing lowest non-zero bit of an unsigned integer。理论上,CAS循环可以优化为纯LL / SC循环。

这个问题也是一个例子(最好的选择)(使用C ++或Java源代码)是CAS重试循环的情况,因为该语言没有满足您需要的原子原语。 (任何通用硬件也没有。)