文章出处:https://blog.csdn.net/liuxuejiang158blog/article/details/17413149
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
#include
#include
#include
#include
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
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
bool is_lock_free() const;
atomic() = default;//默认构造函数,T未初始化,可能后面被atomic_init(atomic
constexpr atomic(T val);//T由val初始化
atomic(const atomic &) = delete;//禁止拷贝
atomic & operator=(const atomic &) = delete;//atomic对象间的相互赋值被禁止,但是可以显示转换再赋值,如atomic
atomic & operator=(const atomic &) volatile = delete;//atomic间不能赋值
T operator=(T val) volatile;//可以通过T类型对atomic赋值,如:atomic
T operator=(T val);
operator T() const volatile;//读取被封装的T类型值,是个类型转换操作,默认内存序是memory_order_seq需要其它内存序则调用load
operator T() const;//如:atomic
T exchange(T val, memory_order = memory_order_seq_cst) volatile;//将T的值置为val,并返回原来T的值
T exchange(T val, memory_order = memory_order_seq_cst);
void store(T val, memory_order = memory_order_seq_cst) volatile;//将T值设为val
void store(T val, memory_order = memory_order_seq_cst);
T load(memory_order = memory_order_seq_cst) const volatile;//访问T值
T load(memory_order = memory_order_seq_cst) const;
bool compare_exchange_weak(T& expected, T val, memory_order = memory_order_seq_cst) volatile;//该函数直接比较原子对象所封装的值与参数 expected 的物理内容,所以某些情况下,对象的比较操作在使用 operator==() 判断时相等,但 compare_exchange_weak 判断时却可能失败,因为对象底层的物理内容中可能存在位对齐或其他逻辑表示相同但是物理表示不同的值(比如 true 和 2 或 3,它们在逻辑上都表示"真",但在物理上两者的表示并不相同)。可以虚假的返回false(和expected相同)。若本atomic的T值和expected相同则用val值替换本atomic的T值,返回true;若不同则用本atomic的T值替换expected,返回false。
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;//
与compare_exchange_weak 不同, strong版本的 compare-and-exchange 操作不允许(spuriously 地)返回 false,即原子对象所封装的值与参数 expected 的物理内容相同,比较操作一定会为 true。不过在某些平台下,如果算法本身需要循环操作来做检查, compare_exchange_weak 的性能会更好。因此对于某些不需要采用循环操作的算法而言, 通常采用compare_exchange_strong 更好
bool compare_exchange_strong(T &, T, memory_order = memory_order_seq_cst);
};
cplusplus给出的例子之一:
// atomic::compare_exchange_weak example:
#include
#include
#include
#include
// a simple global linked list:
struct Node { int value; Node* next; };
std::atomic
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
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
signed char
unsigned char
short
unsigned short
int
unsigned int
long
unsigned long
long long
unsigned long long
char16_t
char32_t
wchar_t
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
如果你需要显式指定内存序,应该使用atomic_store_explicit。所以前缀atomic_表示c风格的原子自由函数,后缀_explicit指定内存序。
5 atomic
std::atomic
bool x=b.load(std::memory_order_acquire);
b.store(true);
x=b.exchange(false,std::memory_order_acq_rel);//更改为false并返回原来的值
compare_exchange_weak/strong函数是保证在比较和交换执行下原子化,但是此函数可能与expected值相等的情形下atomic的T值没有替换为val,这时atomic值未变且返回false,compare_exchange_weak可能失败,特别是线程数多于CPU核心数时compare-exchange这个指令序列可能CPU不能保证原子化,所以经常在循环中:
bool expected=false;
extern atomic
while(!b.compare_exchange_weak(expected,true) && !expected);
compare_exchange_strong可以保证当atomic不等于expected时返回false,不需要循环保护。
std::atomic_flag是lock free的,但是atomic
一个例子的代码片段:读者写者
#include
#include
#include
std::vector
std::atomic
void reader_thread()
{
while(!data_ready.load())
{
std::this_thread::sleep(std::milliseconds(1));
}
std::cout<<”The answer=”< }
void writer_thread()
{
data.push_back(42);//2 由于1和2处发生了data race,所以需要线程同步,可以使用mutex,此处使用atomic
data_ready=true;
}
6 std::atomic
class Foo{};
Foo some_array[5];
std::atomic
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]);
7 整数原子类型(如std::atomic
8 注意std::shared_ptr
std::shared_ptr
void process_global_data()
{
std::shared_ptr
process_data(local);
}
void update_global_data()
{
std::shared_ptr
std::atomic_store(&p,local);
}
---------------------
作者:liuxuejiang158
来源:CSDN
原文:https://blog.csdn.net/liuxuejiang158blog/article/details/17413149
版权声明:本文为博主原创文章,转载请附上博文链接!