有谁能总结一下比较和交换编程的优缺点? (例如多核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中专门讨论这个问题。
答案 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重试循环的情况,因为该语言没有满足您需要的原子原语。 (任何通用硬件也没有。)