Post

🧩 Rust - atomic中的compare_exchange_weak

在看Rust有关atomic的文档的时候,有一个 compare_exchange_weak 方法

1
2
3
4
5
6
7
pub fn compare_exchange_weak(
    &self,
    current: bool,
    new: bool,
    success: Ordering,
    failure: Ordering
) -> Result<bool, bool>

它和 compare_exchange 方法

1
2
3
4
5
6
7
pub fn compare_exchange(
    &self,
    current: bool,
    new: bool,
    success: Ordering,
    failure: Ordering
) -> Result<bool, bool>

函数签名完全一样,那么他俩的区别是什么呢?

首先了解一下compare_exchange是做什么的?

他做的事情就是 compare and swap , 看一下current value 是不是预期的,如果是那么改成 new value,不然的话就失败,而这些是一个原子操作

为什么是一个原子操作呢?是因为底层平台的支持

它和 compare_exchange_weak 的区别就是,后者除了 current value不是预期 导致的失败,后者还允许其他原因导致的失败,

还有能有其他原因???

了解一下LL/SC

比如 x86中的 cas 指令 CMPXCHG,

但是比如在ARM架构中,他没有直接像 x86 这样的 cas指令, 取而代之的是 load-link/store-conditional 两对指令

load-link 你可以想成就是获取这个内存空间的独有的所有权,然后返回这个内存空间的值

store-conditional 会写入一个新值,但是!!! only if, 只有在 ,load-link和这个store-conditional操作之间,这个内存空间没有更新过

有一种情况是,其他线程去更新这个内存空间,但是是同样的值,这样store-conditional 也会失败

这也是ABA问题,而这种问题是 x86 架构 cas 操作无法察觉的

甚至 another load-link也会导致store-conditional失败

Real implementations of LL/SC do not always succeed even if there are no concurrent updates to the memory location in question. Any exceptional events between the two operations, such as a context switch, another load-link, or even (on many platforms) another load or store operation, will cause the store-conditional to spuriously fail.

所以ARM架构是依靠 循环 ll/sc 这样组合来完成 cas 做的事情

失败的情况

所以在 compare_exchange_weak 文档里面可以看到这句话

Unlike AtomicBool::compare_exchange, this function is allowed to spuriously fail even when the comparison succeeds, which can result in more efficient code on some platforms.

所以这里的spuriously fail就是指的上面说的那些情况

因为compare_exchange 只有在 current value 不是 预期的时候失败

而在不同平台下,就比如 ARM ,导致它失败的原因 可能有其他,上面说过了,(我突然想到了数学中,过程不对,结果正确的情况)

compare_exchange_weak 就能允许捕捉到这种失败而提前返回失败,那这样控制权就又交回了我们自己代码手中,从而可以执行自己的逻辑

在x86下 compare_exchange_weak 还是cas指令去完成的

循环重试

那如果用 compare_exchange 呢,因为说了在例如 ARM 架构中是通过 不断的 循环 ll/sc 两对指令来实现cas操作的,那么如果不像 compare_exchange_weak那样允许捕捉到 除了 current value不是预期值的失败,那么它就只能不断的循环重试, 这样就是所谓的效率不高了么

而且,如果我们的代码是这么写的话

1
2
3
4
5
6
while lock
	  .compare_exchange(UNLOCKED,LOCKED,Ordering::Relaxed,Ordering::Relaxed)
	  .is_err()
{
	//
}

那么在ARM下就是循环套循环

正确的姿势

那么如果你已经用了一个循环,那么最好选择 compare_exchange_weak

那么如果没有用循环,

如果你只在乎结果,那么 compare_exchange 就可以

如果你也在乎过程正义,那么选择 compare_exchange_weak, 毕竟 is_err() 可以提前返回 false了

This post is licensed under CC BY 4.0 by the author.