1、标准原子类型
标准原子类型的定义位于头文件<atomic>
内。原子操作的关键用途是取代需要互斥的同步方式,但假设原子操作本身也在内部使用了互斥,就很可能无法达到期望的性能提升。有三种方法来判断一个原子类型是否属于无锁数据结构:
- 所有标准原子类型(
std::atomic_flag
除外,因为它必须采取无锁操作)都具有成员函数is_lock_free()
,若它返回true
则表示给定类型上的操作是能由原子指令直接实现的,若返回false
则表示需要借助编译器和程序库的内部锁来实现。 - C++程序库提供了一组宏:
ATOMIC_BOOL_LOCK_FREE
、ATOMIC_CHAR_LOCK_FREE
、ATOMIC_CHAR16_T_LOCK_FREE
、ATOMIC_CHAR32_T_LOCK_FREE
、ATOMIC_WCHAR_T_LOCK_FREE
、ATOMIC_SHORT_LOCK_FREE
、ATOMIC_INT_LOCK_FREE
、ATOMIC_LONG_LOCK_FREE
、ATOMIC_LLONG_LOCK_FREE
、ATOMIC_POINTER_LOCK_FREE
。宏取值为0
表示对应的std::atomic<>
特化类型从来都不属于无锁结构,取值为1
表示运行时才能确定是否属于无锁结构,取值为2
表示它一直属于无锁结构。 - 从C++17开始,全部原子类型都含有一个静态常量表达式成员变量
X::is_always_lock_free
,功能与上述那些宏相同,用于在编译期判定一个原子类型是否属于无锁结构。当且仅当在所有支持运行该程序的硬件上,原子类型X
全都以无锁结构形式实现,该成员变量的值才为true
。
除了std::atomic_flag
,其余原子类型都是通过模板std::atomic<>
特化得到的。由内建类型特化得到的原子类型,其接口反映出自身性质,例如C++标准没有为普通指针定义位运算(如&=
),所以不存在专为原子化指针而定义的位运算。一些内建类型的std::atomic<>
特化如下表:
原子类型的别名 | 对应的特化 |
---|---|
atomic_bool | std::atomic<bool> |
atomic_char | std::atomic<char> |
atomic_schar | std::atomic<signed char> |
atomic_uchar | std::atomic<unsigned char> |
atomic_int | std::atomic<int> |
atomic_uint | std::atomic<unsigned> |
atomic_short | std::atomic<short> |
atomic_ushort | std::atomic<unsigned short> |
atomic_long | std::atomic<long> |
atomic_ulong | std::atomic<unsigned long> |
atomic_llong | std::atomic<long long> |
atomic_ullong | std::atomic<unsigned long long> |
atomic_char16_t | std::atomic<char16_t> |
atomic_char32_t | std::atomic<char32_t> |
atomic_wchar_t | std::atomic<wchar_t> |
原子类型对象无法复制,也无法赋值,但可以接受内建类型赋值,也支持隐式地转换成内建类型。需要注意的是:按照C++惯例,赋值操作符通常返回一个引用,指向接受赋值的目标对象;而原子类型的赋值操作符不返回引用,而是按值返回(该值属于对应的非原子类型)。
2、原子操作
各种原子类型上可以执行的操作如下表所示:
操作 | atomic_flag | atomic<bool> | atomic<T*> | 整数原子类型 | 其它原子类型 |
---|---|---|---|---|---|
test_and_set | Y | ||||
clear | Y | ||||
is_lock_free | Y | Y | Y | Y | |
load | Y | Y | Y | Y | |
store | Y | Y | Y | Y | |
exchange | Y | Y | Y | Y | |
compare_exchange_weak, compare_exchange_strong | Y | Y | Y | Y | |
fetch_add, += | Y | Y | |||
fetch_sub, -= | Y | Y | |||
fetch_or, |= | Y | ||||
fetch_and, &= | Y | ||||
fetch_xor, ^= | Y | ||||
++, -- | Y | Y |
2.1、操作std::atomic_flag
std::atomic_flag
是最简单的标准原子类型,表示一个布尔标志,它只有两种状态:成立或置零。std::atomic_flag
对象必须由宏ATOMIC_FLAG_INIT
初始化,它把标志初始化为置零状态,例如:std::atomic_flag f = ATOMIC_FLAG_INIT;
。如果不进行初始化,则std::atomic_flag
对象的状态是未指定的。std::atomic_flag
有两个成员函数:
clear()
:将标志清零。test_and_set()
:获取旧值并设置标志成立。
使用std::atomic_flag
实现一个自旋锁的示例如下:
class spinlock_mutex
{
std::atomic_flag flag;
public:
spinlock_mutex() : flag(ATOMIC_FLAG_INIT) {}
void lock()
{
while (flag.test_and_set());
}
void unlock()
{
flag.clear();
}
};
2.2、操作std::atomic<bool>
相比于std::atomic_flag
,std::atomic<bool>
是一个功能更齐全的布尔标志。尽管它也无法拷贝构造或拷贝赋值,但还是能依据非原子布尔量创建其对象,也能接受非原子布尔量的赋值:
std::atomic<bool> b(true);
b = false;
store()
是存储操作,可以向原子对象写入值。load()
是载入操作,可以读取原子对象的值。exchange()
是“读-改-写”操作,它获取原有的值,然后用自行选定的新值作为替换。
std::atomic<bool> b;
bool x = b.load();
b.store(true);
x = b.exchange(false);
compare_exchange_weak()
与compare_exchange_strong()
被称为“比较-交换”操作,它们的作用是:使用者给定一个期望值,原子变量将它和自身的值进行比较,如