为什么只有atomic_flag是保证无锁的
引言
在学习了C++中的原子操作后顿时感受到其贴近底层的强大,但是在学习的过程中还是有很多点并没有搞的十分的明白透彻,这篇文章的标题就是其中之一,在各大博客平台搜寻未果,在查阅了一些资料后对于这个问题有了一些理解 遂进行记录 对有同样疑惑的朋友提供一个参考方向
首先抛出问题,就是为什么在C++原子库中仅保证atomic_flag是保证无锁的,而atomic< int>,atomic< bool>不是呢
要解决这个问题我们首先来看看它们其中的结构 我们拿atomic< bool>和atomic_flag做个对比
//首先atomic_flag继承__atomic_flag_base
struct atomic_flag : public __atomic_flag_base {
...
}
//__atomic_flag_base的结构
struct __atomic_flag_base
{
__atomic_flag_data_type _M_i;
};
//而__atomic_flag_data_type是这样的
#if __GCC_ATOMIC_TEST_AND_SET_TRUEVAL == 1
typedef bool __atomic_flag_data_type;
#else
typedef unsigned char __atomic_flag_data_type;
#endif
//我们可以看到atomic<bool>是atomic泛型的一个特化
template<>
struct atomic<bool>
{
private:
__atomic_base<bool> _M_base;
...
}
//我们再来看看_atomic_base<>
template<typename _ITp>
struct __atomic_base
{
private:
typedef _ITp __int_type;
static constexpr int _S_alignment =
sizeof(_ITp) > alignof(_ITp) ? sizeof(_ITp) : alignof(_ITp);
alignas(_S_alignment) __int_type _M_i;
...
}
其实我们可以看到内部的存储其实都是bool类型,并没有什么区别
但是源码中有这样一段注释很有意思
// NB: Assuming _ITp is an integral scalar type that is 1, 2, 4, or
// 8 bytes, since that is what GCC built-in functions for atomic
// memory access expect.
其实就是说1,2,4,8个字节是编译器期望的值
下面是stackoverflow上对这个问题的高赞回答
-
First of all, you are perfectly allowed to have something like std::atomic<very_nontrivial_large_structure>, so std::atomic as such cannot generally be guaranteed to be lock-free (although most specializations for trivial types like bool or int probably could,on most systems). But that is somewhat unrelated.
首先 您被允许有std::atomic< 很大的且重要的结构>,
所以这样的std::atomic通常不被保证是无锁的(在大多数系统上,尽管很多琐碎的数据类型例如bool,int等都是可以的(笔者:上面源码中的注释)) -
No other type requires lock-free operations, and hence the atomic_flag type is the minimum hardware-implemented type needed to conform to this standard. The remaining types can be emulated with atomic_flag, though with less than ideal properties. In other words, it’s the minimum thing that must be guaranteed on every platform, so it’s possible to implement the standard correctly.
没有其他类型需要无锁操作,因此atomic_flag类型是符合该标准的最小的硬件实现类型,剩下的类型可以用atomic_flag类型进行模仿,尽管其效率通常低于预想类型.换句话说,这是在每一个平台上必须被保证的最低要求,因此可以正确的实施该标准.
下面是知乎的回答
- 为什么atomic_flag是无锁的
其实应该是反过来,std::atomic因为T的不确定所以无法做到一个可以应用到各种T的无锁实现,所以std::atomic就不能保证无锁。但是当T是bool或者int的时候就可以通过特定的cpu指令来保证操作在cpu的层面是atomic的。所以就有atomic_flag - 为什么atomic< bool>不保证无锁呢
因为atomic是范型的类,就是同样的逻辑应该在不同的T上。语言的标准上不能定义得这么细,atomic抽象出来就是提供原子性的功能,就不要求无锁了。当然我们实现的时候可以单独将T是bool和int的atomic特制化成无锁的,不过这样就不方便标准的指定,还不如引入另外新的概念来提供无锁的版本
结论
atomic< T>不保证无锁是因为防止T是一个很大的结构,其实atomic< T>在1,2,4,8个字节的都是很大可能保证无锁的,而atomic_flag是硬件的最低标准,保证无锁.
2020年11月20日:
有朋友私信问道atomic_flag如何实现无锁特性,我认为回答问题的同时也是补充这篇文章的好机会,我的回答是这样的:
基本目前的原子操作就我知道的大多以CAS实现,这个是硬件支持的,比如x86_64的实现就是CMPXCHG指令,Linux中原子自增的源码就是这样。再底层一点就是所谓的总线锁和缓存一致性了。本文中说到了1,2,4,8,我想这就是对应于不同机器的寄存器大小,对应于8,16,32,64位的寄存器,同时也是CMPXCHG指令的操作数。
笔者水平有限,答案仅供参考。
参考:
https://stackoverflow.com/questions/30256472/why-only-stdatomic-flag-is-guaranteed-to-be-lock-free
https://en.cppreference.com/w/cpp/atomic/atomic_flag