原子类型是对数据的一种封装,可以防止数据竞争,同步多线程间的内存访问。<atomic>头文件中主要包含两个类:atomic
和atomic_flag
,可以在自包含类中实现原子类型的所有特性。同时头文件还声明了一组与C语言原子支持兼容的C风格类型和函数。
1. atomic
包含特定类型(T)的原子类型对象。原子对象的主要特点是:多个线程访问对象所包含的值不会引起数据竞争。此外,原子对象能够通过指定不同的内存顺序来同步对线程中其他非原子对象的访问。
内存顺序(memory_order):
typedef enum memory_order {
memory_order_relaxed, // relaxed
memory_order_consume, // consume
memory_order_acquire, // acquire
memory_order_release, // release
memory_order_acq_rel, // acquire/release
memory_order_seq_cst // sequentially consistent
} memory_order;
用作原子性函数的参数,以指定如何同步不同线程上的其他操作。在一个原子操作完成之前其他线程无法访问该原子对象。但是每个线程可能在原子对象本身之外的内存位置执行操作:这些其他操作可能对其他线程产生副作用。memory_order允许指定操作的内存顺序,以确定线程之间如何同步这些(可能是非原子的)副作用,使用原子操作作为同步点:
- memory_order_relaxed
在某个时间点执行该原子操作。这是最松散的内存顺序,不能保证不同线程中的内存访问是按照原子操作的顺序进行的。 - memory_order_consume
一旦释放线程中所有对内存的访问(这些访问依赖于释放操作(并且对加载线程有副作用))都发生了,则命令执行该操作。 - memory_order_acquire
一旦释放线程中的所有内存访问(这些访问对加载线程有副作用)都发生了,则命令执行该操作。 - memory_order_release
该操作在消耗或获取操作之前执行,作为对内存的其他访问的同步点,这些访问可能对加载线程产生副作用。 - memory_order_acq_rel
该操作加载获取和存储释放(如上面的memory_order_acquisition和memory_order_release所定义)。 - memory_order_seq_cst
该操作以顺序一致的方式进行排序:使用该内存顺序的所有操作都是在所有可能对涉及的其他线程产生副作用的内存访问都已经发生之后才执行的。
这是最严格的内存顺序,通过非原子内存访问确保线程交互之间的意外副作用最小。
对于消耗和获取负载,顺序一致的存储操作被认为是释放操作。
1.1 std::atomic::atomic
(1). 默认构造
构造一个未初始化的原子对象。(后面可通过调用atomic_init
进行初始化)
(2). 初始化构造
构造一个用val初始化的原子对象。
(3). 复制构造 [禁用]
禁用复制构造函数,原子对象不可复制/移动。
示例:
// 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!
1.2 std::atomic::operator=
(1). 给原子对象赋值(val)。这个操作是原子性的,并且使用顺序一致性(memory_order_seq_cst)
(2). 原子对象不可复制赋值,但是可以隐式转换。
示例:
// atomic::operator=/operator T example:
#include <iostream> // std::cout
#include <atomic> // std::atomic
#include <thread> // std::thread, std::this_thread::yield
std::atomic<int> foo = 0;
void set_foo(int x) {
foo = x;
}
void print_foo() {
while (foo==0) { // wait while foo=0
std::this_thread::yield();
}
std::cout << "foo: " << foo << '\n';
}
int main ()
{
std::thread first (print_foo);
std::thread second (set_foo,10);
first.join();
second.join();
return 0;
}
输出:
foo: 10
1.3 std::atomic::is_lock_free
判断原子对象是否 lock-free 状态。无锁状态的原子对象不会导致其他线程在访问时被阻塞(可能使用某种类型的事务内存)。
1.4 std::atomic::store
用 val 替换原子对象中的值。该操作是原子性的,通过 sync 指定内存顺序。
sync的可选项:memory_order_relaxed、memory_order_release 、memory_order_seq_cst
示例:
// atomic::load/store example
#include <iostream> // std::cout
#include <atomic> // std::atomic, std::memory_order_relaxed
#include <thread> // std::thread
std::atomic<int> foo (0);
void set_foo(int x) {
foo.store(x,std::memory_order_relaxed); // set value atomically
}
void print_foo() {
int x;
do {
x = foo.load(std::memory_order_relaxed); // get value atomically
} while (x==0);
std::cout << "foo: " << x << '\n';
}
int main ()
{
std::thread first (print_foo);
std::thread second (set_foo,10);
first.join();
second.join();
return 0;
}
输出:
foo: 10
1.5 std::atomic::load
返回原子对象中的值。该操作是原子性的,通过 sync 指定内存顺序。
sync的可选项:memory_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_seq_cst
1.6 std::atomic::operator T
返回原子对象中的值。这是一个类型转换符,取出原子对象中类型为 T 的值。这个操作是原子性的,并且使用顺序一致性(memory_order_seq_cst)
示例:
// atomic::operator=/operator T example:
#include <iostream> // std::cout
#include <atomic> // std::atomic
#include <thread> // std::thread, std::this_thread::yield
std::atomic<int> foo = 0;
std::atomic<int> bar = 0;
void set_foo(int x) {
foo = x;
}
void copy_foo_to_bar () {
while (foo==0) std::this_thread::yield();
bar = static_cast<int>(foo);
}
void print_bar() {
while (bar==0) std::this_thread::yield();
std::cout << "bar: " << bar << '\n';
}
int main ()
{
std::thread first (print_bar);
std::thread second (set_foo,10);
std::thread third (copy_foo_to_bar);
first.join();
second.join();
third.join();
return 0;
}
输出:
bar: 10
1.7 std::atomic::exchange
用 val 替换原子对象中的值,并返回原来存储的值。操作是原子性的,整个(读-修改-写)操作完成之前其他线程无法访问。
sync的可选项:所有6个
示例:
// atomic::exchange example
#include <iostream> // std::cout
#include <atomic> // std::atomic
#include <thread> // std::thread
#include <vector> // std::vector
std::atomic<bool> ready (false);
std::atomic<bool> winner (false);
void count1m (int id) {
while (!ready) {} // wait for the ready signal
for (int i=0; i<1000000; ++i) {} // go!, count to 1 million
if (!winner.exchange(true)) { 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!
1.8 std::atomic::compare_exchange_weak
将原子对象存储的值与预期值比较:
- 如果为true,用 val 替换原子对象的值(像
store
)。 - 如果为false,它用包含的值替换预期值。
函数总是读取原子对象存储的值,如果比较结果是“true”,就替换存储值。整个过程是原子性的。
(2)中使用的内存顺序取决于比较结果:true->sucess; flase->failure。
注意该函数比较的是原子对象和预期值中的物理内容(physical contents),这可能导致使用操作符==比较相等的值的在这里比较失败(如果基础类型具有相同值的填充位、陷阱值或替代表示形式)。
与compare_exchange_strong
不同,该week版本允许错误的返回false,即使原子对象存储值与预期值相等(operator==)。对于某些循环算法,这可能是可接受的行为,并且可能在某些平台上显著提高性能。对于这些虚假的失败(spurious failures),函数返回false,但不修改预期的值。对于非循环算法,通常首选compare_exchange_strong
。
sync可选项:所有6个
示例:
// atomic::compare_exchange_weak example:
#include <iostream> // std::cout
#include <atomic> // std::atomic
#include <thread> // std::thread
#include <vector> // std::vector
// a simple global linked list:
struct Node { int value; Node* next; };
std::atomic<Node*> list_head (nullptr);
void append (int val) { // append an element to the list
Node* oldHead = list_head;
Node* newNode = new Node {val,oldHead};
// what follows is equivalent to: list_head = newNode, but in a thread-safe way:
while (!list_head.compare_exchange_weak(oldHead,newNode))
newNode->next = oldHead;
}
int main ()
{
// spawn 10 threads to fill the linked list:
std::vector<std::thread> threads;
for (int i=0; i<10; ++i) threads.push_back(std::thread(append,i));
for (auto& th : threads) th.join();
// print contents:
for (Node* it = list_head; it!=nullptr; it=it->next)
std::cout << ' ' << it->value;
std::cout << '\n';
// cleanup:
Node* it; while (it=list_head) {list_head=it->next; delete it;}
return 0;
}
输出(顺序可能不同):
9 8 7 6 5 4 3 2 1 0
1.9 std::atomic::compare_exchange_strong
将原子对象存储的值与预期值比较:
- 如果为true,用 val 替换原子对象的值(像
store
)。 - 如果为false,它用包含的值替换预期值。
函数总是读取原子对象存储的值,如果比较结果是“true”,就替换存储值。整个过程是原子性的。
(2)中使用的内存顺序取决于比较结果:true->sucess; flase->failure。
注意该函数比较的是原子对象和预期值中的物理内容(physical contents),这可能导致使用操作符==比较相等的值的在这里比较失败(如果基础类型具有相同值的填充位、陷阱值或替代表示形式)。
与compare_exchange_week
不同,当期望值与对象存储的值相等时,这个强版本必须始终返回true,不允许虚假的失败。但是,在某些机器上,对于某些在循环中检查这个的算法,compare_exchange_weak
可能有更高的性能表现。
sync的可选项:所有6个
1.10 专门化计算操作
- std::atomic::fetch_add
存储的值加上 val 并返回之前的值,整个操作是原子性的。如果第二个参数使用默认值,那么这个函数相当于atomic::operator+=
。 - std::atomic::fetch_sub
存储的值减去 val 并返回之前的值,整个操作是原子性的。如果第二个参数使用默认值,那么这个函数相当于atomic::operator-=
。 - std::atomic::fetch_and
存储的值与 val 进行 “与操作” 并返回之前的值,整个操作是原子性的。如果第二个参数使用默认值,那么这个函数相当于atomic::operator&=
。 - std::atomic::fetch_or
存储的值与 val 进行 “或操作” 并返回之前的值,整个操作是原子性的。如果第二个参数使用默认值,那么这个函数相当于atomic::operator|=
。 - std::atomic::fetch_xor
存储的值与 val 进行 “异或操作” 并返回之前的值,整个操作是原子性的。如果第二个参数使用默认值,那么这个函数相当于atomic::operator^=
。 - std::atomic::operator++
递增所包含值的值,并返回生成的值(1)或之前的值(2),整个操作是原子性的。 - std::atomic::operator–
递减所包含值的值,并返回生成的值(1)或之前的值(2),整个操作是原子性的。 - std::atomic::operator (comp. assign.)
整数(1)和指针(2)类型的专门化原子复合赋值:
应用对应的复合赋值操作符到相应的原子对象并返回操作之前的值,所有的操作都是原子性的。
2 std::atomic_flag
原子标志(atomic_flag)是bool型的原子对象,只支持两种操作:test-and-set
和clear
。原子标志是无锁的(这是惟一一种在所有库实现上保证无锁的类型)。
示例:
// using atomic_flag as a lock
#include <iostream> // std::cout
#include <atomic> // std::atomic_flag
#include <thread> // std::thread
#include <vector> // std::vector
#include <sstream> // std::stringstream
std::atomic_flag lock_stream = ATOMIC_FLAG_INIT;
std::stringstream stream;
void append_number(int x) {
while (lock_stream.test_and_set()) {}
stream << "thread #" << x << '\n';
lock_stream.clear();
}
int main ()
{
std::vector<std::thread> threads;
for (int i=1; i<=10; ++i) threads.push_back(std::thread(append_number,i));
for (auto& th : threads) th.join();
std::cout << stream.str();
return 0;
}
输出(结果可能不同):
thread #1
thread #2
thread #3
thread #4
thread #5
thread #6
thread #7
thread #8
thread #9
thread #10
2.1 std::atomic_flag::atomic_flag
构建一个原子标志对象。除非显式用 ATOMIC_FLAG_INIT初始化,否则该对象在构造时处于未指定的状态(既不是 set 也不是 clear)。这个显式初始化是在默认构造时实现还是后期调用取决于具体的库实现。原子标志对象不可复制也不可转移。
示例:
// constructing atomics: atomic<bool> vs atomic_flag
#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); // can be checked without being set
std::atomic_flag winner = ATOMIC_FLAG_INIT; // always set when checked
void count1m (int id) {
while (!ready) { std::this_thread::yield(); } // wait for the ready signal
for (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 #6 won!
2.2 std::atomic_flag::test_and_set
设置原子标志(设为true),并返回它是否在调用之前已被设置,若已被设置返回true,否则false。操作是原子性的。
sync的可选项:全部6个
示例:
// atomic_flag as a spinning lock
#include <iostream> // std::cout
#include <atomic> // std::atomic_flag
#include <thread> // std::thread
#include <vector> // std::vector
#include <sstream> // std::stringstream
std::atomic_flag lock_stream = ATOMIC_FLAG_INIT;
std::stringstream stream;
void append_number(int x) {
while (lock_stream.test_and_set()) {}
stream << "thread #" << x << '\n';
lock_stream.clear();
}
int main ()
{
std::vector<std::thread> threads;
for (int i=1; i<=10; ++i) threads.push_back(std::thread(append_number,i));
for (auto& th : threads) th.join();
std::cout << stream.str();
return 0;
}
输出(可能不同):
thread #1
thread #2
thread #3
thread #4
thread #5
thread #6
thread #7
thread #8
thread #9
thread #10
2.3 std::atomic_flag::clear
清除原子标志(设为false)。下一次调用 atomic_flag::test_and_set
会返回false。该操作是原子性的。
sync的可选项:memory_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_release、memory_order_seq_cst