贡献者: int256
C++ 11 中引入的原子变量(atomic)是在多线程编程中的常用同步机制。它可以避免竟态条件(race condition)和死锁(dead lock)问题。它可以保证对共享变量的操作在执行时不会被其他线程的操作干扰。同时,性能也要高于使用锁的实现。
举个例子,以下 C++ 代码:
// C++ 11
#include <iostream>
#include <thread>
using namespace std;
int n(0);
void cnt1e4() {
for(int i(1); i <= 1e4; ++i) ++ n;
}
int main() {
thread ths[100];
for(thread &thr: ths) thr = thread(cnt1e4);
for(thread &thr: ths) thr.join();
cout << n << endl;
return 0;
}
理论输出应为 $10^4 \times 100 = 1000000$,而实际输出总小一些,例如在某计算机上运行三次结果分别为:
这类问题尤其在大量不同线程对于同一变量进行操作时出现,更多的线程数量可能导致更大的误差。以两个线程为例,从计算机对变量的操作的角度来考虑这个问题: 对于某一个线程,电脑此时要对 $n$ 执行 $+1$ 操作,顺序为:
而当有两个线程或是更多个线程的时候,可能会出现以下情况:
此时 $n$ 的值本应为 $n_{old} + 2$,实际却是 $n_{old}+1$,当大量不同线程同时对 $n$ 进行类似操作时更容易出现这类问题,所以引入了原子变量与原子操作的方法。这个方法的实现性能要远高于使用锁的实现。
atomic
是一个模板类,可以通过 atomic<_typName> varName;
来声明类型为 _typName
的、变量名为 varName
的原子变量,前提是 _typName
类型合法。
对于所有的原子变量都有以下四个常用的公开成员函数:
store
:原子地以非原子对象替换原子对象的值;
load
:原子地获得原子对象的值;
is_lock_free
:检查原子对象是否免锁;
compare_exchange_weak
、compare_exchange_strong
:原子地比较原子对象与非原子参数的值,若相等则进行交换,若不相等则进行加载。
对于大部分情况下,要对原子变量操作,一般方法是先通过 load
加载原子变量目前存储的值,然后操作后执行 compare_exchange
,特别注意,这里不使用 store
函数。store
函数是不安全的。
在一些平台,弱形式的 CAS(Compare And Set)函数,也就是 compare_exchange_weak
函数性能可能更高。两个 CAS 函数均建议使用 do-while 循环进行操作。但是弱形式的函数可能会有 “出乎意料(Unexpected)” 的返回,比如在字段值和期待值一样的时候却返回了 false
。
下面通过介绍一些使用 atomic
类型的例子来介绍其用法,并给出一些例程。
对于合法的自定义类,例如下面的 counter
,可以直接使用模板类声明原子类型。进行操作时使用 CAS 函数,也就是 compare_exchange_strong
或 compare_exchange_weak
进行保存。
// C++ 11
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
struct counter {
int a{};
int b{};
};
atomic<counter> cnt;
void cnt1e4() {
for(int i(1); i <= 1e4; ++i) {
counter desired, expected;
desired = cnt.load();
do {
expected = desired;
// then do some thing...
expected.a ++;
expected.b += 2;
} while(! cnt.compare_exchange_strong(desired, expected)) ;
// while(! cnt.compare_exchange_weak(desired expected)) ;
}
}
int main() {
thread ths[100];
for(thread &thr: ths) thr = thread(cnt1e4);
for(thread &thr: ths) thr.join();
cout << cnt.load().a << ' ' << cnt.load().b << endl;
return 0;
}
原子变量对整数类型进行了特化,具体的有:
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
。
这些类型可以直接使用 atomic_类型
的方式直接声明变量。
其中,unsigned
的类型加前缀 u
,long long
缩写为 llong
。
例如:atomic_ullong a;
。
对于这些整数变量,提供了专门的 fetch_add
,fetch_sub
、fetch_or
等函数可以直接进行原子操作。例如:a.fetch_add(3);
可以对原子变量 a
原子地加 $3$ 并赋值给它。
关于原子变量,有下列相关的 C++ 标准:
C++ 标准在头文件(标头)中提供了以下关于 atomic
的定义:
template< class T > struct atomic;
template< class U > struct atomic<U*>;
template<class U> struct atomic<std::shared_ptr<U>>;
template<class U> struct atomic<std::weak_ptr<U>>;
原子类型要求是满足可复制构造 (CopyConstructible)、可复制赋值 (CopyAssignable) 的可平凡复制 (TriviallyCopyable) 类型。 其中,可复制构造是指该类型的实例可以从左值表达式进行复制构造,可复制赋值是指该类型的实例可以从左值表达式复制赋值,可平凡复制类型有下表的要求。
对于以下任意值为 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
。
都是不合法的。这些模板类定义在标头文件 <type_traits>
中。
 
 
 
 
 
 
 
 
 
 
 
友情链接: 超理论坛 | ©小时科技 保留一切权利