原子比较交换操作
C111
Atomically compares the object representation (until C++20)value representation (since C++20) of the object pointed to by obj with that of the object pointed to by expected, and if those are bitwise-equal, replaces the former with desired (performs read-modify-write operation). Otherwise, loads the actual value pointed to by obj into *expected (performs load operation). Copying is performed as if by std::memcpy.
The memory models for the read-modify-write and load operations are succ and fail respectively. The (1-2) versions use std::memory_order_seq_cst by default.
compare_exchange(*obj, *expected, desired)
The result of the comparison: true if *obj was equal to *expected, false otherwise.
接口操作是比较并交换。
如果 *obj == *expected
,则更新 *obj
到值 desired
,*expected
不变。否则将 *expected
更新为当前读取到的 *obj
值。但是实际情况并不和文档描述得那么一致,特别是 *expected
的更新问题。
atomic_compare_exchange_weak
The weak forms of the functions are allowed to fail spuriously, that is, act as if *obj != *expected
even if they are equal. When a compare-and-exchange is in a loop, the weak version will yield better performance on some platforms.
int raw_atomic_int_cas_weak(raw_atomic_int_t *v, int old_v, int new_v)
{
return atomic_compare_exchange_weak(v, &old_v, new_v);
}
通过看指令(armeb),ip
就是 r12
。我们可以看到 strex
是可以失败的,r3
表示 strex
操作成功与否,0 表示成功,成功则返回 true
。可以注意到失败时不会更新 *expected
的值,始终是第一次读取到的 *expected
。这与文档描述是有出入的。失败场景为:
*obj != *expected
*obj == *expected
而strex 失败
因此外层函数不应当依赖
*expected
的值来判断成功与否。
尝试把 expected 返回回来看下是否有差异
int raw_atomic_int_cas_weak_with_old_val(raw_atomic_int_t *v, int old_v, int new_v)
{
atomic_compare_exchange_weak(v, &old_v, new_v);
return old_v;
}
这里有一个坑就是没有判断 strex
的操作结果,前面提到了这个操作是可能失败的。因此比较操作成功而交换操作失败时,外层的函数如果以 old_v
作为操作成功的依据,会误以为成功了。此时得到的返回值始终是读取到的当前 *obj
。外层函数不应当依赖 *expected
的值来判断操作是否成功。
因此正确的写法可以是
#include <stdatomic.h>
typedef atomic_int raw_atomic_int_t;
/* raw refcnt no release-function */
struct raw_refcnt_nr {
raw_atomic_int_t count;
};
int raw_atomic_int_cas_weak(raw_atomic_int_t *v, int old_v, int new_v)
{
return atomic_compare_exchange_weak(v, &old_v, new_v);
}
static inline int raw_atomic_int_read(raw_atomic_int_t *v)
{
return *v; /* this is atomic and seq_cst */
}
int raw_refcnt_nr_get(struct raw_refcnt_nr *ref_nr)
{
int old_val, success;
while (1) {
old_val = raw_atomic_int_read(&ref_nr->count);
success = raw_atomic_int_cas_weak(&ref_nr->count, old_val, old_val + 1);
if (success) {
/* return new counter */
return old_val + 1;
}
}
__builtin_unreachable();
}
如果希望返回 old_val 来判断成功与否,则可以这么写。
int raw_atomic_int_cas(raw_atomic_int_t *v, int old_v, int new_v)
{
int success;
volatile int old = old_v;
do {
success = atomic_compare_exchange_weak(v, &old_v, new_v);
} while (success == 0 && old_v == old);
return old_v;
}
int raw_refcnt_nr_get(struct raw_refcnt_nr *ref_nr)
{
int old, ok;
while (1) {
old = raw_atomic_int_read(&ref_nr->count);
ok = raw_atomic_int_cas(&ref_nr->count, old, old + 1);
if (ok == old && ok >= 0) {
/* return new counter */
return old + 1;
}
}
__builtin_unreachable();
}
https://en.cppreference.com/w/cpp/atomic/atomic_compare_exchange ↩︎