__sync_val_compare_and_swap 与 __sync_bool_compare_and_swap

And*_*ker 6 c c++ gcc atomic built-in

我一直在思考这两个函数的返回值。__sync_bool_compare_and_swap 函数的返回值似乎有明显的好处,即我可以用它来判断是否发生了交换操作。但是我看不到 __sync_val_compare_and_swap 的返回值的良好使用。

首先,让我们有一个函数签名供参考(来自 GCC 文档减去 var args):

type __sync_val_compare_and_swap (type *ptr, type oldval type newval);
Run Code Online (Sandbox Code Playgroud)

我看到的问题是 __sync_val_compare_and_swap 的返回值是 *ptr 的旧值。准确地说,它是在适当的内存屏障到位后通过此函数的实现看到的值。我明确指出这是为了满足这样一个事实,即在调用 __sync_val_compare_and_swap 和执行指令以强制执行内存屏障之间,*ptr 的值很容易改变。

现在,当函数返回时,我可以用那个返回值做什么?尝试将其与 *ptr 进行比较是没有意义的,因为现在可以在其他线程上更改 *ptr。同样,比较 newval 和 *ptr 也对我没有真正的帮助(除非我锁定 *ptr 这可能首先会破坏我对原子的使用)。

所以真正留给我做的就是询问是否返回值 == oldval,这实际上是(请参阅下面的警告)询问是否发生了交换操作。所以我可以只使用 __sync_bool_compare_and_swap。

我刚刚提到的警告是,我在这里看到的唯一细微差别是,这样做并没有告诉我交换是否发生,它只是告诉我在释放内存屏障之前的某个时刻 *ptr 具有相同的值为 newval。我正在考虑 oldval == newval 的可能性(尽管我很难找到一种有效实现该函数的方法,以便它可以首先检查这些值而不是交换它们是否相同,所以这可能是一个有争议的问题)。但是,我看不到在呼叫站点知道这种差异会对我产生影响的情况。事实上,我无法想象我会将 oldval 和 newval 设置为相等的情况。

我的问题是:

是否存在使用 __sync_val_compare_and_swap 和 __sync_bool_compare_and_swap 不等效的用例,即是否存在一种情况提供的信息多于另一个的情况?

在旁边

我考虑这个的原因是我发现了一个 __sync_val_compare_and_swap 的实现,它有一个竞争:

inline int32_t __sync_val_compare_and_swap(volatile int32_t* ptr, int32_t oldval, int32_t newval)
{
    int32_t ret = *ptr;
    (void)__sync_bool_compare_and_swap(ptr, oldval, newval);
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

竞争是在 ret 中存储 *ptr,因为 *ptr 可能会在调用 __sync_bool_compare_and_swap 之前发生变化。这让我意识到,就sync_bool_compare_and_swap而言,似乎没有一种安全的方式(没有额外的障碍或锁)来实现__sync_val_compare_and_swap。这让我认为前者必须提供比后者更多的“信息”,但根据我的问题,我认为它确实没有。

R..*_*R.. 7

由 提供的操作__sync_val_compare_and_swap总是可以实现的__sync_bool_compare_and_swap(当然另一个方向显然是可能的),所以在功率方面两者是等效的。然而__sync_val_compare_and_swap,就 而言实施__sync_bool_compare_and_swap并不是很有效。它看起来像:

for (;;) {
    bool success = __sync_bool_compare_and_swap(ptr, oldval, newval);
    if (success) return oldval;
    type tmp = *ptr;
    __sync_synchronize();
    if (tmp != oldval) return tmp;
}
Run Code Online (Sandbox Code Playgroud)

需要额外的工作,因为您可以观察到失败__sync_bool_compare_and_swap但随后从中读取一个新值*ptr恰好匹配oldval

至于为什么您可能更喜欢这种__sync_val_compare_and_swap行为,导致失败的值可能会为您提供一个更有效地重试操作的起点,或者可能表明某些不会“重试”的操作的失败原因是有意义的。例如,请参阅pthread_spin_trylockmusl libc 中的代码(我是作者):

http://git.musl-libc.org/cgit/musl/tree/src/thread/pthread_spin_trylock.c?id=afbcac6826988d12d9a874359cab735049c17500

a_cas相当于__sync_val_compare_and_swap。在某些方面,这是一个愚蠢的例子,因为它只是通过使用旧值来保存分支或条件移动,但在其他情况下,多个旧值是可能的,并且知道导致操作失败的那个很重要。