我一直在考虑这两个函数的返回值。 __sync_bool_compare_and_swap函数的返回值似乎有明显的好处,即我可以用它来判断交换操作是否发生。但是我看不到__sync_val_compare_and_swap的返回值的好用。
首先,让我们有一个函数签名供参考(来自GCC docs减去var args):
type __sync_val_compare_and_swap (type *ptr, type oldval type newval);
我看到的问题是__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不等效的用例,即是否存在提供比另一个更多信息的情况?
ASIDE
我之所以考虑这个问题,是因为我发现了一个__sync_val_compare_and_swap的实现方式,其中sync_bool_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;
}
正在将* ptr存储在ret中,因为* ptr可能会在调用__sync_bool_compare_and_swap之前发生变化。它让我意识到,就sync_bool_compare_and_swap而言,我似乎没有一种安全的方法(没有额外的障碍或锁定)来实现__sync_val_compare_and_swap。这让我觉得前者必须提供比后者更多的“信息”,但根据我的问题,我没有看到它确实存在。
答案 0 :(得分:4)
__sync_val_compare_and_swap
提供的操作始终可以__sync_bool_compare_and_swap
实现(当然其他方向显然也可以),所以就 power 而言是等价的。但是,根据__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;
}
需要额外的工作,因为您可能会发现__sync_bool_compare_and_swap
失败,但会从*ptr
中读取符合oldval
的新值。
对于为什么您可能更喜欢__sync_val_compare_and_swap
行为,导致失败的值可能会为您提供一个起点,可以更有效地重试操作,或者可能表示有意义的原因某些操作的失败,不会被重试"。作为示例,请参阅musl libc中pthread_spin_trylock
的代码(我是作者):
a_cas
相当于__sync_val_compare_and_swap
。在某些方面,这是一个愚蠢的例子,因为它只是通过使用旧值来保存分支或条件移动,但是在其他情况下可能存在多个旧值并且知道导致操作失败的问题。