C++中对共享数据的存取在并发条件下可能会引起data race的undifined行为,需要限制并发程序以某种特定的顺序执行,有两种方式:使用mutex保护共享数据,原子操作:针对原子类型操作要不一步完成,要么不做,不可能出现操作一半被切换CPU,这样防止由于多线程指令交叉执行带来的可能错误。非原子操作下,某个线程可能看见的是一个其它线程操作未完成的数据。
1 关于bool的原子化
1.1 std::atomic_flag是一个bool原子类型有两个状态:set(flag=true) 和 clear(flag=false),必须被ATOMIC_FLAG_INIT初始化此时flag为clear状态,相当于静态初始化。一旦atomic_flag初始化后只有三个操作:test_and_set,clear,析构,均是原子化操作。atomic_flag::test_and_set检查flag是否被设置,若被设置直接返回true,若没有设置则设置flag为true后再返回false。atomic_clear()清楚flag标志即flag=false。不支持拷贝、赋值等操作,这和所有atomic类型一样,因为两个原子类型之间操作不能保证原子化。atomic_flag的可操作性不强导致其应用局限性,还不如atomic
使用atomic_flag作为简单的自旋锁例子:本线程可以对flag设置了就跳出循环,避免使用mutex导致线程阻塞
#include // std::cout
#include // std::atomic_flag
#include // std::thread
#include // std::vector
#include // std::stringstream
std::atomic_flag lock_stream = ATOMIC_FLAG_INIT;//flag处于clear状态,没有被设置过
std::stringstream stream;
void append_number(int x) {
while (lock_stream.test_and_set()) {}//检查并设置是个原子操作,如以前没有设置过则退出循环,
//每个线程都等待前面一个线程将lock_stream状态清楚后跳出循环
stream << "thread #" << x << '\n';
lock_stream.clear();}
int main (){
std::vector 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;
}
采用class封装可以用于lock_guard或unique_lock,但是最好不要将此用于任何竞态条件下,这是一个busy loop!
class spinlock_mutex
{
std::atomic_flag flag;
public:
spinlock_mutex():
flag(ATOMIC_FLAG_INIT){}
void lock()
{
while(flag.test_and_set(std::memory_order_acquire));
}
void unlock()
{
flag.clear(std::memory_order_release);
}
};
2 atomic
atomic
template < class T > struct atomic {
bool is_lock_free() const volatile;//判断atomic中的T对象是否为lock free的,若是返回true。lock free(锁无关)指多个线程并发访问T不会出现data race,任何线程在任何时刻都可以不受限制的访问T
bool is_lock_free() const;
atomic() = default;//默认构造函数,T未初始化,可能后面被atomic_init(atomic* obj,T val )函数初始化
constexpr atomic(T val);//T由val初始化
atomic(const atomic &) = delete;//禁止拷贝
atomic & operator=(const atomic &) = delete;//atomic对象间的相互赋值被禁止,但是可以显示转换再赋值,如atomic a=static_cast(b)这里假设atomic b
atomic & operator=(const atomic &) volatile = delete;//atomic间不能赋值
T operator=(T val) volatile;//可以通过T类型对atomic赋值,如:atomic a;a=10;
T operator=(T val);
operator T() const volatile;//读取被封装的T类型值,是个类型转换操作,默认内存序是memory_order_seq需要其它内存序则调用load
operator T() const;//如:atomic a,a==0或者cout<
cplusplus给出的例子之一:
// atomic::compare_exchange_weak example:
#include // std::cout
#include // std::atomic
#include // std::thread
#include // std::vector
// a simple global linked list:
struct Node { int value; Node* next; };
std::atomic list_head (nullptr);
void append (int val) { // append an element to the list
Node* newNode = new Node {val,list_head};
// next is the same as: list_head = newNode, but in a thread-safe way:
while (!list_head.compare_exchange_weak(newNode->next,newNode)) {}
// (with newNode->next updated accordingly if some other thread just appended another node)
}
int main ()
{
// spawn 10 threads to fill the linked list:
std::vector 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
3 std::atomic针对整数和指针的特化:
不能像传统那样拷贝和赋值,可以通过内置成员函数load(),store(),exchange()完成赋值,支持复合赋值运算,自增自减运算,还有特有的fetch系列函数
整型特化:
specializations | additional member functions |
---|---|
char extended integral types (if any) |
atomic::fetch_add atomic::fetch_sub atomic::fetch_and atomic::fetch_or atomic::fetch_xor atomic::operator++ atomic::operator-- operator (comp. assign.) |
指针特化:
specializations | additional member functions |
---|---|
U* (for any type U) |
atomic::fetch_add atomic::fetch_sub atomic::operator++ atomic::operator-- operator (comp. assign.) |
T fetch_add (T val, memory_order sync = memory_order_seq_cst) noexcept;//整型
T fetch_add (ptrdiff_t val, memory_order sync = memory_order_seq_cst) noexcept;//指针
将原子对象的封装值加 val,并返回原子对象的旧值(适用于整形和指针类型的 std::atomic 特化版本),整个过程是原子的
T fetch_and (T val, memory_order sync = memory_order_seq_cst) noexcept;//将原子对象的封装值按位与 val,并返回原子对象的旧值(只适用于整型的 std::atomic 特化版本),整个过程是原子的。
T fetch_or (T val, memory_order sync = memory_order_seq_cst) noexcept;//将原子对象的封装值按位或 val,并返回原子对象的旧值(只适用于整型的 std::atomic 特化版本),整个过程是原子的。
fetch_xor (T val, memory_order sync = memory_order_seq_cst) noexcept;//将原子对象的封装值按位异或 val,并返回原子对象的旧值(只适用于整型的 std::atomic 特化版本),整个过程是原子的。
operator++
pre-increment (1)
T operator++() volatile noexcept;
T operator++() noexcept;
post-increment (2)
T operator++ (int) volatile noexcept;
T operator++ (int) noexcept;
自增运算符重载, 第一种形式 (1) 返回自增后的值(即前缀++),第二种形式(2) 返回自增前的值(即后缀++),适用于整形和指针类型的 std::atomic 特化版本。
operator--
自减运算符重载, 第一种形式 (1) 返回自减后的值(即前缀--),第二种形式(2) 返回自减前的值(即后缀--),适用于整形和指针类型的 std::atomic 特化版本。
atomic::operator (comp. assign.)
复合赋值运算符重载,主要包含以下形式:
if T is integral (1)
T operator+= (T val) volatile noexcept;
T operator+= (T val) noexcept;
T operator-= (T val) volatile noexcept;
T operator-= (T val) noexcept;
T operator&= (T val) volatile noexcept;
T operator&= (T val) noexcept;
T operator|= (T val) volatile noexcept;
T operator|= (T val) noexcept;
T operator^= (T val) volatile noexcept;
T operator^= (T val) noexcept;
if T is pointer (2)
T operator+= (ptrdiff_t val) volatile noexcept;
T operator+= (ptrdiff_t val) noexcept;
T operator-= (ptrdiff_t val) volatile noexcept;
T operator-= (ptrdiff_t val) noexcept;
以上各个 operator 都会有对应的 fetch_* 操作,详细见下表:
操作符 成员函数 支持类型
复合赋值 等价于 整型 指针类型 其他类型
+ atomic::operator+= atomic::fetch_add 是 是 否
- atomic::operator-= atomic::fetch_sub 是 是 否
& atomic::operator&= atomic::fetch_and 是 否 否
| atomic::operator|= atomic::fetch_or 是 否 否
^ atomic::operator^= atomic::fetch_xor 是 否 否
4 C风格的atomic类型及其操作,有点繁杂这里不赘述了,参看:点击打开链接和点击打开链接。前面成员函数前缀atomic_形成原子函数,函数的第一个参数必须是原子类型,如:
atomic_store (volatile atomic* obj, T val)
如果你需要显式指定内存序,应该使用atomic_store_explicit。所以前缀atomic_表示c风格的原子自由函数,后缀_explicit指定内存序。
5 atomic
std::atomic b;
bool x=b.load(std::memory_order_acquire);
b.store(true);
x=b.exchange(false,std::memory_order_acq_rel);//更改为false并返回原来的值
bool expected=false;
extern atomic b; // set somewhere else
while(!b.compare_exchange_weak(expected,true) && !expected);
#include
#include
#include
std::vector data;
std::atomic data_ready(false);
void reader_thread()
{
while(!data_ready.load())
{
std::this_thread::sleep(std::milliseconds(1));
}
std::cout<<”The answer=”<强制线程间有个顺序关系
data_ready=true;
}
class Foo{};
Foo some_array[5];
std::atomic p(some_array);
Foo* x=p.fetch_add(2);
assert(x==some_array);
assert(p.load()==&some_array[2]);
x=(p-=1);
assert(x==&some_array[1]);
assert(p.load()==&some_array[1]);
std::shared_ptr p;
void process_global_data()
{
std::shared_ptr local=std::atomic_load(&p);
process_data(local);
}
void update_global_data()
{
std::shared_ptr local(new my_data);
std::atomic_store(&p,local);
}