C++11 对整型变量原子操作的支持
在 C++ 98/03 标准中,如果想对整型变量进行原子操作,要么利用操作系统提供的相关原子操作 API,要么利用对应操作系统提供的锁对象来对变量进行保护,无论是哪种方式,编写的代码都无法实现跨平台操作,例如上一小介绍的Interlocked系列 API 代码仅能运行于 Windows 系统,无法移植到 Linux 系统。C++ 11 新标准发布以后,改变了这种困境,新标准提供了对整型变量原子操作的相关库,即 std::atomic ,这是一个模板类型:
template<class T> struct atomic;你可以传入具体的整型类型(如bool、char、short、int、uint等)对模板进行实例化,实际上 stl 库也提供了这些实例化的模板类型:
| 类型别名 | 定义 |
|---|---|
| std::atomic_bool | std::atomic<bool> |
| std::atomic_char | std::atomic<char> |
| std::atomic_schar | std::atomic<signed char> |
| std::atomic_uchar | std::atomic<unsigned char> |
| std::atomic_short | std::atomic<short> |
| std::atomic_ushort | std::atomic<unsigned short> |
| std::atomic_int | std::atomic<int> |
| std::atomic_uint | std::atomic<unsigned int> |
| std::atomic_long | std::atomic<long> |
| std::atomic_ulong | std::atomic<unsigned long> |
| std::atomic_llong | std::atomic<long long> |
| std::atomic_ullong | std::atomic<unsigned long long> |
| std::atomic_char16_t | std::atomic<char16_t> |
| std::atomic_char32_t | std::atomic<char32_t> |
| std::atomic_wchar_t | std::atomic<wchar_t> |
| std::atomic_int8_t | std::atomic<std::int8_t> |
| std::atomic_uint8_t | std::atomic<std::uint8_t> |
| std::atomic_int16_t | std::atomic<std::int16_t> |
| std::atomic_uint16_t | std::atomic<std::uint16_t> |
| std::atomic_int32_t | std::atomic<std::int32_t> |
| std::atomic_uint32_t | std::atomic<std::uint32_t> |
| std::atomic_int64_t | std::atomic<std::int64_t> |
| std::atomic_uint64_t | std::atomic<std::uint64_t> |
上表中仅列出了 C++11 支持的常用的整型原子变量,完整的列表读者可以参考这里:std::atomic - cppreference.com。
有了 C++ 语言本身对原子变量的支持以后,我们就可以“愉快地”写出跨平台的代码了,我们来看一段代码:
#include <atomic> #include <stdio.h> int main() { std::atomic<int> value; value = 99; printf("%d\n", (int)value); //自增1,原子操作 value++; printf("%d\n", (int)value); return 0; }以上代码可以同时在 Windows 和 Linux 平台上运行,但是有读者可能会根据个人习惯将上述代码写成如下形式:
#include <atomic> #include <stdio.h> int main() { std::atomic<int> value = 99; printf("%d\n", (int)value); //自增1,原子操作 value++; printf("%d\n", (int)value); return 0; }代码仅仅做了一点简单的改动,这段代码在 Windows 平台上运行良好,但是在 Linux 平台上会无法编译通过(这里指的是在支持 C++ 11语法的 g++ 编译中编译),提示错误是:
error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)’产生这个错误的原因是 “std::atomic<int> value = 99;” 这一行代码调用的是 std::atomic 的拷贝构造函数,对于 int 型,其形式一般如下:
std::atomic<int>::atomic(const std::atomic<int>& rhs);而根据 C++ 11 的规范,这个拷贝构造函数是默认使用=delete语法禁止编译器生成的,g++ 遵循了这个标准,参见这里 https://zh.cppreference.com/w/cpp/atomic/atomic/operator:
atomic& operator=( const atomic& ) = delete;所以 Linux 平台上编译器会提示错误,而 Windows 的 VC++ 编译器没有遵循这个规范。而对于代码:
value = 99;g++ 和 VC++ 同时实现规范中的:
T operator=( T desired )因此,如果读者想利用 C++ 11 提供的 std::atomic 库编写跨平台的代码,在使用 std::atomic 提供的方法时建议参考官方 std::atomic 提供的接口说明来使用,而不是想当然地认为一个方法在此平台上可以运行,在另外一个平台也能有相同的行为,避免出现上面说的这种情形。
上述代码中之所以可以对 value 进行自增(++)操作是因为std::atomic类内部重载了operator =运算符,除此以外,std::atomic提供了大量有用的方法,这些方法您一定会觉得似曾相似:
| 方法名 | 方法说明 |
|---|---|
| operator= | 存储值于原子对象 |
| store | 原子地以非原子对象替换原子对象的值 |
| load | 原子地获得原子对象的值 |
| exchange | 原子地替换原子对象的值并获得它先前持有的值 |
| compare_exchange_weak compare_exchange_strong | 原子地比较原子对象与非原子参数的值,若相等则进行交换,若不相等则进行加载 |
| fetch_add | 原子地将参数加到存储于原子对象的值,并返回先前保有的值 |
| fetch_sub | 原子地从存储于原子对象的值减去参数,并获得先前保有的值 |
| fetch_and | 原子地进行参数和原子对象的值的逐位与,并获得先前保有的值 |
| fetch_or | 原子地进行参数和原子对象的值的逐位或,并获得先前保有的值 |
| fetch_xor | 原子地进行参数和原子对象的值的逐位异或,并获得先前保有的值 |
| operator++ operator++(int) operator-- operator--(int) | 令原子值增加或减少一 |
| operator+= operator-= operator&= operator|= operator^= | =加、减,或与原子值进行逐位与、或、异或 |