现代C++ [5]: 原子操作
前言
std::atomic
模板的实例化或全特化可以定义一个原子的数据类型。它提供了多线程间的原子操作。所谓原子操作,一般是指在逻辑上的不可分割的操作(物理上并不一定只有一条指令)。原子操作是无锁类型,但不代表无需等待,当多个线程同时竞争同一原子变量时,仍要为了同步而等待。但原子操作的开销要低于加解锁。
原子操作(std::atomic)
原型
// since C++11 |
对原子对象的访问可以建立线程间同步,并按照std::memory_order
指定的顺序对非原子内存访问进行排序。
std::atomic
既不可复制,也不可移动。
特化
原始模板
原始模板std::atomic
可以被任何可平凡拷贝(TriviallyCopyable)的类型T
实例化,若T
为class类型,则需要满足可拷贝构造(CopyConstructible)和可拷贝赋值(CopyAssignable)。
可拷贝构造需要满足可移动构造,即可移动构造是可拷贝构造的必要不充分条件;可拷贝赋值和可移动赋值同理。
所以,如果以下值为false
,则T
不可以用于实例化std::atomic
:
-
std::is_trivially_copyable<T>::value
-
std::is_copy_constructible<T>::value
-
std::is_move_constructible<T>::value
-
std::is_copy_assignable<T>::value
-
std::is_move_assignable<T>::value
偏特化
C++标准库为以下类型提供了std::atomic
的偏特化,这些偏特化拥有原始模板没有的一些特性:
std::atomic<U*>
偏特化:适用所有的指针类型。这些偏特化拥有标准布局(standard layout)、平凡的默认构造函数(trivial default constructors, (until C++20))和平凡的析构函数(trivial destructors)。除了为所有原子类型提供的操作之外,这些偏特化还支持适用于指针类型的原子算术操作,例如fetch_add
、fetch_sub
。std::atomic<std::shared_ptr>>
和std::atomic<std::weak_ptr>>
偏特化:适用于智能指针的偏特化。
针对整数类型的特化
当使用以下整数类型之一实例化时,std::atomic
提供了适用于整数类型的其他原子操作,如 fetch_add
, fetch_sub
, fetch_and
, fetch_or
, fetch_xor
:
- 字符类型:
char
,char8_t
(since C++20),char16_t
,char32_t
和wchar_t
; - 标准有符号整型:
signed char
,short
,int
,long
和long long
; - 标准无符号整型:
unsigned char
,unsigned short
,unsigned int
,unsigned long
和unsigned long long
; - 在头文件
<cstdint>
中typedef的任何其他整数类型;
同样,这些特化拥有标准布局(standard layout)、平凡的默认构造函数(trivial default constructors, (until C++20))和平凡的析构函数(trivial destructors)。有符号整型的算术操作被定义为使用二的补码。
针对浮点数类型的特化(since C++20)
当使用标准浮点类型之一,如float
,double
,long double
或扩展的浮点类型(since C++23),进行实例化时,std::atomic
提供了适用于浮点数类型的其他原子操作,如 fetch_add
, fetch_sub
。
类型别名
using atomic_bool = atomic<bool>;
其他类型别名自行查表。
成员函数
函数名 | 解释 |
---|---|
ctor | 构造一个原子对象 |
operator= |
将值存储到原子对象中 |
is_lock_free |
检查原子对象是否无锁 |
store |
用非原子参数原子地替换原子对象的值 |
load |
原子地获得原子对象的值 |
operator T |
从一个原子对象中加载值 |
exchange |
原子地替换原子对象的值,并获得先前保存的值 |
compare_exchange_weak compare_exchange_strong |
将原子对象的值与非原子参数进行原子比较,如果相等则执行原子交换,如果不相等,则执行原子获取 |
wait (C++20) |
阻塞线程,直到被通知(notified)并且原子值更改 |
notify_one (C++20) |
通知至少一个等待原子对象的线程 |
notify_all (C++20) |
通知所有因等待原子对象而被阻塞的线程 |
特化成员函数
函数名 | 解释 |
---|---|
fetch_add |
将参数原子地添加到存储在原子对象中的值,并获得先前保存的值 |
fetch_sub |
从存储在原子对象中的值中原子地减去参数,并获得先前保存的值 |
fetch_and |
原子地在参数和原子对象的值之间执行逐位“与”运算,并获得先前保存的值 |
fetch_or |
原子地在参数和原子对象的值之间执行逐位“或”,并获得先前保存的值 |
fetch_xor |
原子地在参数和原子对象的值之间执行逐位“异或”,并获得先前保存的值 |
operator++ operator++(int) operator-- operator--(int) |
将原子值增加或减少一 |
operator+= operator-= operator&= operator|= operator^= |
将原子值与参数相加、相减或执行位与、位或、位异或 |
参考文献
[1] https://en.cppreference.com/w/cpp/atomic/atomic
[2] https://en.cppreference.com/w/cpp/language/memory_model
[3] https://en.cppreference.com/w/cpp/atomic/memory_order