现代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_weakcompare_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








![现代硬件算法[8.8]: 时空局部性](/images/pd_09.webp)
![现代硬件算法[8.7]: 缓存遗忘算法](/images/pd_08.webp)
![现代硬件算法[8.6]: 淘汰策略](/images/pd_07.webp)
![现代硬件算法[8.5]: 列表排名](/images/pd_06.webp)
![现代硬件算法[8.4]: 外部排序](/images/pd_05.webp)