为什么 atomic 提供 compare_exchange_strong?
Why atomic provide the compare_exchange_strong?
现在我正在研究 Android 上的 AtomicInteger
class。
这个Javaclass有两个方法
public final void set(int newValue) {
value = newValue;
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
问题一:
set方法直接存储新值,所以这个方法不是原子的?如果我们希望结果在许多线程中都是正确的,我们需要使用 compareAndSet
方法?
我已经阅读了有关 AtomicInteger.compareAndSet()
的源代码。最后它调用 std::atomic<T>::compare_exchange_strong
方法。 incrementAndGet
方法调用 compareAndSet。
问题二:
我读过一些关于 CAS 的文章,compare_exchange_strong()
是原子的,store()
也是原子的。所以我想知道,为什么我们不直接使用 store,为什么需要 CAS 方法?
CAS 的要点在于它允许您存储 条件 原始值的值。一个简单的store当然可以原子地完成,但它可能不是你想要的。
考虑简单的 "atomic increment" 操作:
- 加载当前值。
- 计算新值。
- 存储新值。
如果您为此过程使用不相关的原子加载和存储,那么两个线程可以读取、计算和存储相同的值,因此其中一个增量会丢失!加载和存储是简单的原子操作,而 CAS 是原子 read-modify-write (RMW) 操作。 RMW 比简单的加载和存储更复杂。 RMW 不仅保证正确生成简单的值,而且保证正确应用复杂的逻辑操作。
让我们使用 CAS 实现原子增量来演示其工作原理:
// Effect: x += n, returns old value of x.
int inc(int n, std::atomic<int> & x)
{
int old_val = x.load();
for (;;) {
int new_val = old_val + n;
if (x.compare_exchange_weak(old_val, new_val)) {
return old_val;
}
// Note: If the exchange fails, old_val is updated
// to the current value of x.
}
}
这里的关键点是这个操作必须是一个循环,因为当我们计算我们的暂定结果(new_val = old_val + n
)时,值old_val
可能已经过时了,因为其他线程已经修改了它。所以我们必须循环 for 直到我们有机会在 x
当前持有旧值的情况下应用新值。这就是 CAS 的要点:它存储一个新值,条件是旧值是我们认为的。
关于"strong"与"weak"交换的区别:如果你希望你的算法无条件成功,你总是想要一个像我展示的循环,你使用弱交换。不同之处在于弱形式可能 "fail spuriously",即 return false
,即使存储的值是预期值。 (这种放宽允许在某些平台上执行更有效的指令。)强形式可能更昂贵但不会虚假地失败,并且在您执行 "optimistic" CAS 但不关心是否它成功了。这在某些并发数据结构(例如队列)中很有用,在这些结构中,线程会尽最大努力提供帮助,但不需要保证成功。
现在我正在研究 Android 上的 AtomicInteger
class。
这个Javaclass有两个方法
public final void set(int newValue) {
value = newValue;
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
问题一:
set方法直接存储新值,所以这个方法不是原子的?如果我们希望结果在许多线程中都是正确的,我们需要使用 compareAndSet
方法?
我已经阅读了有关 AtomicInteger.compareAndSet()
的源代码。最后它调用 std::atomic<T>::compare_exchange_strong
方法。 incrementAndGet
方法调用 compareAndSet。
问题二:
我读过一些关于 CAS 的文章,compare_exchange_strong()
是原子的,store()
也是原子的。所以我想知道,为什么我们不直接使用 store,为什么需要 CAS 方法?
CAS 的要点在于它允许您存储 条件 原始值的值。一个简单的store当然可以原子地完成,但它可能不是你想要的。
考虑简单的 "atomic increment" 操作:
- 加载当前值。
- 计算新值。
- 存储新值。
如果您为此过程使用不相关的原子加载和存储,那么两个线程可以读取、计算和存储相同的值,因此其中一个增量会丢失!加载和存储是简单的原子操作,而 CAS 是原子 read-modify-write (RMW) 操作。 RMW 比简单的加载和存储更复杂。 RMW 不仅保证正确生成简单的值,而且保证正确应用复杂的逻辑操作。
让我们使用 CAS 实现原子增量来演示其工作原理:
// Effect: x += n, returns old value of x.
int inc(int n, std::atomic<int> & x)
{
int old_val = x.load();
for (;;) {
int new_val = old_val + n;
if (x.compare_exchange_weak(old_val, new_val)) {
return old_val;
}
// Note: If the exchange fails, old_val is updated
// to the current value of x.
}
}
这里的关键点是这个操作必须是一个循环,因为当我们计算我们的暂定结果(new_val = old_val + n
)时,值old_val
可能已经过时了,因为其他线程已经修改了它。所以我们必须循环 for 直到我们有机会在 x
当前持有旧值的情况下应用新值。这就是 CAS 的要点:它存储一个新值,条件是旧值是我们认为的。
关于"strong"与"weak"交换的区别:如果你希望你的算法无条件成功,你总是想要一个像我展示的循环,你使用弱交换。不同之处在于弱形式可能 "fail spuriously",即 return false
,即使存储的值是预期值。 (这种放宽允许在某些平台上执行更有效的指令。)强形式可能更昂贵但不会虚假地失败,并且在您执行 "optimistic" CAS 但不关心是否它成功了。这在某些并发数据结构(例如队列)中很有用,在这些结构中,线程会尽最大努力提供帮助,但不需要保证成功。