关注公众号获取更多信息:
C++11提供了一个原子类型std::atomic<T>,可以使用任意类型作为模板参数,C++11内置了整型的原子变量,可以方便的使用原子变量,使用原子变量就不用互斥变量来保护该变量了。
每个 std::atomic 模板的实例化和全特化定义一个原子类型。若一个线程写入原子对象,同时另一线程从它读取,则行为良好定义(数据竞争的细节我们会在下节--内存模型里讲解)
另外,对原子对象的访问可以建立线程间同步,并按 std::memory_order 所对非原子内存访问定序。
需要注意的是,std::atomic 既不可复制亦不可移动。
由于std::atomic 是一个模板,C++11中定义了该模板特化的一些要求:
主模板
主 std::atomic 模板可用任何满足可复制构造 (CopyConstructible) 及可复制赋值 (CopyAssignable) 的可平凡复制 (TriviallyCopyable) 类型 T 特化。若下列任何值为 false 则程序为谬构:
-
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
std::atomic<bool> 使用初等模板。它保证是标准布局结构体。
偏特化
标准库为下列类型提供 std::atomic 模板的特化,它们拥有初等模板所不拥有的额外属性:
对所有指针类型的部分特化 std::atomic<U*> 。这些特化拥有标准布局、平凡默认构造函数 (C++20 前)和平凡析构函数。除了为所有原子类型提供的操作,这些特化额外支持适合指针类型的原子算术运算,例如 fetch_add 、 fetch_sub 。
对整数类型的特化
以下列整数类型之一实例化时, std::atomic
提供适合于整数类型的额外原子操作,例如 fetch_add
、 fetch_sub
、 fetch_and
、 fetch_or
、 fetch_xor
:
-
-
字符类型 char 、 char8_t (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 所需的额外整数类型。
-
另外,结果的 std::atomic<Integral>
特化拥有标准布局、平凡默认构造函数 (C++20 前)和平凡析构函数。定义有符号整数算术为使用补码;无未定义结果。
atomic 用法
这次我们就不一一介绍atomic的API了,我们从atomic的定义反观atomic的用法:
template < class T > struct atomic {
bool is_lock_free() const volatile;
bool is_lock_free() const;
void store(T, memory_order = memory_order_seq_cst) volatile;
void store(T, memory_order = memory_order_seq_cst);
T load(memory_order = memory_order_seq_cst) const volatile;
T load(memory_order = memory_order_seq_cst) const;
operator T() const volatile;
operator T() const;
T exchange(T, memory_order = memory_order_seq_cst) volatile;
T exchange(T, memory_order = memory_order_seq_cst);
bool compare_exchange_weak(T &, T, memory_order, memory_order) volatile;
bool compare_exchange_weak(T &, T, memory_order, memory_order);
bool compare_exchange_strong(T &, T, memory_order, memory_order) volatile;
bool compare_exchange_strong(T &, T, memory_order, memory_order);
bool compare_exchange_weak(T &, T, memory_order = memory_order_seq_cst) volatile;
bool compare_exchange_weak(T &, T, memory_order = memory_order_seq_cst);
bool compare_exchange_strong(T &, T, memory_order = memory_order_seq_cst) volatile;
bool compare_exchange_strong(T &, T, memory_order = memory_order_seq_cst);
atomic() = default;
constexpr atomic(T);
atomic(const atomic &) = delete;
atomic & operator=(const atomic &) = delete;
atomic & operator=(const atomic &) volatile = delete;
T operator=(T) volatile;
T operator=(T);
};
可以看出,我们常用的API如下:
(构造函数) | 构造原子对象 (公开成员函数) |
operator= | 存储值于原子对象 (公开成员函数) |
is_lock_free | 检查原子对象是否免锁 (公开成员函数) |
store | 原子地以非原子对象替换原子对象的值 (公开成员函数) |
load | 原子地获得原子对象的值 (公开成员函数) |
operator T | 从原子对象加载值 (公开成员函数) |
exchange | 原子地替换原子对象的值并获得它先前持有的值 (公开成员函数) |
compare_exchange_weakcompare_exchange_strong | 原子地比较原子对象与非原子参数的值,若相等则进行交换,若不相等则进行加载 (公开成员函数) |
除了这些常用的API,我们常用的API还有一些特化的API(之前我们说过,只有整数类型和指针类型有特化,所以这些API只有整数类型和指针类型可用):
fetch_add | 原子地将参数加到存储于原子对象的值,并返回先前保有的值 (公开成员函数) |
fetch_sub | 原子地从存储于原子对象的值减去参数,并获得先前保有的值 (公开成员函数) |
fetch_and | 原子地进行参数和原子对象的值的逐位与,并获得先前保有的值 (公开成员函数) |
fetch_or | 原子地进行参数和原子对象的值的逐位或,并获得先前保有的值 (公开成员函数) |
fetch_xor | 原子地进行参数和原子对象的值的逐位异或,并获得先前保有的值 (公开成员函数) |
operator++operator++(int)operator--operator--(int) | 令原子值增加或减少一 (公开成员函数) |
operator+=operator-=operator&=operator|=operator^= | 加、减,或与原子值进行逐位与、或、异或 (公开成员函数) |
例子
下面我们用一个例子结束本文:
// constructing atomics
#include <iostream> // std::cout
#include <atomic> // std::atomic, std::atomic_flag, ATOMIC_FLAG_INIT
#include <thread> // std::thread, std::this_thread::yield
#include <vector> // std::vector
std::atomic<bool> ready (false);
std::atomic_flag winner = ATOMIC_FLAG_INIT;
void count1m (int id) {
while (!ready) { std::this_thread::yield(); } // wait for the ready signal
for (volatile int i=0; i<1000000; ++i) {} // go!, count to 1 million
if (!winner.test_and_set()) { std::cout << "thread #" << id << " won!\n"; }
};
int main ()
{
std::vector<std::thread> threads;
std::cout << "spawning 10 threads that count to 1 million...\n";
for (int i=1; i<=10; ++i) threads.push_back(std::thread(count1m,i));
ready = true;
for (auto& th : threads) th.join();
return 0;
}
可能的输出:
spawning 10 threads that count to 1 million...
thread #7 won!