![c5533c3f2d871d2e9841f84432fc45a1.png](https://i-blog.csdnimg.cn/blog_migrate/d0b84d31a8bc07e14a5c9ca344a65229.jpeg)
在看c++11的CAS用法的时候,主要是产生了两个问题:
- compare_swap_strong 与 compare_swap_weak 有啥区别?
- c++11 CAS原语系列后面还有两个
memory_order
参数,有什么作用?
/*
* @brief:compare & swap(CAS)。如果等于expect则swap,否则就返回--是否交换成功, 注意expect如果不相等,会把当前值写入到expected里面。
* 相比于strong,weak可能会出现[spurious wakeup](<http://en.wikipedia.org/wiki/Spurious_wakeup>).
* @param 若x等于expect,则设置为desired 返回true,
* 否则最新值写入expect,返回false
*/
class atomic {
bool compare_exchange_strong(T& expect /*用来比较的值*/, T desired/*用来设置的值*/)
bool compare_exchange_weak(T& expect, T desired)
}
compare_swap_strong 与 compare_swap_weak
看一下compare_swap_strong()
的实现是如何的?
首先介绍一下几个定义好的与操作,实际上就是几个enum
// 几个与操作是经过了重载的。
/// Enumeration for memory_order
typedef enum memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
} memory_order;
enum __memory_order_modifier {
__memory_order_mask = 0x0ffff,
__memory_order_modifier_mask = 0xffff0000,
__memory_order_hle_acquire = 0x10000,
__memory_order_hle_release = 0x20000
};
// & operator
constexpr memory_order
operator&(memory_order __m, __memory_order_modifier __mod) {
return memory_order(__m & int(__mod));
}
可以简单地理解为几个常量在操作。接下来看真正的实现部分。
/c++/atomic 头文件
compare_exchange_weak(__pointer_type& __p1,
__pointer_type __p2,
memory_order __m1,
memory_order __m2) noexcept {
return _M_b.compare_exchange_strong(__p1, __p2, __m1, __m2);
}
// atomic_base
_GLIBCXX_ALWAYS_INLINE bool
compare_exchange_strong(__pointer_type& __p1, __pointer_type __p2,
memory_order __m1,
memory_order __m2) noexcept
{
memory_order __b2 = __m2 & __memory_order_mask;
memory_order __b1 = __m1 & __memory_order_mask;
__glibcxx_assert(__b2 != memory_order_release);
__glibcxx_assert(__b2 != memory_order_acq_rel);
__glibcxx_assert(__b2 <= __b1);
return __atomic_compare_exchange_n(&_M_p, &__p1, __p2, 0, __m1, __m2);
}
最后发现__atomic_compare_exchange_n
是由GCC
内置的函数。
gcc 内置
Built-in Function:
bool __atomic_compare_exchange_n(
type *ptr, // 需要进行比较的ptr
type *expected, // 旧的值,会返回ptr里面的值
type desired, // 想要设置的新的值
bool weak, // 强一致,还是弱一致
int success_memorder, // 成功时的内存序
int failure_memorder // 失败时的内存序
)
weak与strong
参考文档gcc内置CAS说明
详细的说明如下,如果有耐心那么可以直接看完。如果没有耐心,可以直接看一下我自己的结论。
- weak = true的时候,对应c++11的
compare_exchange_weak
函数。 - weak = false的时候,对应的是
compare_exchange_strong
函数。
区别这两个函数的区别在于,weak
在有的平台上(注意,是有的平台,这里不包括x86)会存在失败的可能性。即,当*ptr == *expected
依然有可能什么都不做而返回false
。
所以,在x86平台来说,这两者可以说是没什么区别。只是如果想要代码可移值性好,那么采用compare_exchange_weak
并且使用循环来判断,那么是一种比较好的办法。
结论就是:
- 想要性能,使用
compare_exchange_weak
+循环来处理。 - 想要简单,使用
compare_exchange_strong
。 - 如果是x86平台,两者没区别
- 如果想在移值的时候,拿到高性能,用
compare_exchange_weak
。
详细的说明
需要注意的是,weak = true
表示弱CAS,在这种情况下,就是交换成功,也有可能返回失败。
在某些平台上,即使 atomic的值和expected一樣,weak 版仍有可能会失败。但是绝大多数情況不会失敗,且 weak 版的运行比strong版快。所以若本来就需要在循环里执行 compare-and-swap,用 weak 版较有效率;反之,只执行一次的話,用 strong 版可以省去不必要的循环。
所以weak和strong的区别在于,weak仍然在*ptr == expected
的时候,执行依然会有小概率失败。也就是说, 即使*ptr == expected
,此时也不会发生值的设置,也会返回false
。(不会是设置成功了,但是返回的是false)。
Many targets only offer the strong variation and ignore the parameter. When in doubt, use the strong variation.
一般而言,当你拿不准,就使用strong
的版本。
If desired is written into *ptr then true is returned and memory is affected according to the memory order specified by success_memorder. There are no restrictions on what memory order can be used here.
Otherwise, false is returned and memory is affected according to failure_memorder. This memory order cannot be ATOMIC_RELEASE nor ATOMIC_ACQ_REL. It also cannot be a stronger order than that specified by success_memorder.
如果需要写入* ptr则返回true,并根据success_memorder指定的内存顺序影响内存。 这里可以使用什么内存顺序没有限制。
否则,返回false并根据failure_memorder影响内存。 此内存顺序不能是ATOMIC_RELEASE和ATOMIC_ACQ_REL 。 它也不可能比success_memorder指定的顺序更强大。
两个内存序
这个函数系列有趣的是,后面还有两个有趣的memory_order
序。那么这两个memory_order
起什么作用呢?
结论就是,针对x86平台来说,取什么值都是一样的。可以拿一段简单的代码进行尝试,注意查看生成的汇编代码。
#include <pthread.h>
#include <stdbool.h>
extern int a_lock;
int a_lock = 0;
static bool lock(int *l)
{
int tmp = 0;
return __atomic_compare_exchange_n(l, &tmp, 1,
true , __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
}
static void unlock(int *l)
{
__atomic_store_n(l, 0, __ATOMIC_SEQ_CST);
}
int main(int argc, char *argv[])
{
lock(&a_lock);
unlock(&a_lock);
}
生成汇编代码。
cc x.c -O0 -S -o x0.S # 无优化版本
cc x.c -O3 -S -o x.S # 优化版本
可以尝试去修改一下__atomic_compare_exchange_n
后面两个内存序。可以发现最后生成的汇编代码没有什么区别。
小结
- 为了跨平台性以及高性能,那么使用
compare_exchange_weak
好的办法。 - 后面的两个内存序,使用默认的
std::memory_order_seq_cst
即可。这是因为,在x86平台上,微调这两个参数生成的代码都是一样的,没有什么收益。其他弱内存序的cpu上面,可能每种cpu得到的最合适的这两个参数都会有所不同。为了避免去适配各个cpu,那么一种偷懒的办法就是直接使用std::memory_order_seq_cst
。
一句话就是,直接使用var.compare_exchange_weak(a,b);
这种形式最省事。比如:
void link_nodes_atomic(node * new_top_node, node * end_node)
{
tagged_node_handle old_tos = tos.load(detail::memory_order_relaxed);
for (;;) {
tagged_node_handle new_tos (pool.get_handle(new_top_node), old_tos.get_tag());
end_node->next = pool.get_handle(old_tos);
if (tos.compare_exchange_weak(old_tos, new_tos))
break;
}
}
references
- Lockless Programming Considerations
- Sequential consistency
- c++11 atmoic