std::atomic<>
的每个实例化和完全特化表示不同线程可以同时操作(它们的实例)的类型,而不会引发未定义的行为;另外,std::atomic<>
通过允许各种 memory orders 为您提供更多控制权指定同步和排序约束。我前面两篇文章介绍了有关 cpp 11 原子和内存模型的更多信息,感兴趣的可以去阅读下。
对于整型和指针这些的原子类型,您可能会使用 overloaded arithmetic operators或 another set of them 做以下简单加减操作:
std::atomic<int> value(0);
value++; //This is an atomic op
value += 5; //And so is this
但是,上面这些操作忽略了我们前面介绍了内存模型 memory orders 带来的性能提升。因为操作符语法不允许你指定内存顺序,这些操作只能使用 std::memory_order_seq_cst
来执行,因为这是 cpp 11 中所有原子操作的默认顺序。它保证所有原子操作之间的顺序一致性(总全局排序)。在某些情况下,这可能无法满足要求,因此您可能需要使用更明确的形式:
std::atomic<long> value {0};
value.fetch_add(2, std::memory_order_relaxed); // Atomic, but there are no synchronization or ordering constraints
value.fetch_add(2, std::memory_order_release); // Atomic, performs 'release' operation
而且,我们都知道计算机更擅长处理整数以及指针,并且X86架构的CPU还提供了指令级的CAS操作。cpp11为了充分发挥计算的特长,针对非浮点数值(std::atmoic<integral>
)及指针(std::atomic<T\*>
)进行了特化,以提高原子操作的性能。特化后的atomic在通用操作的基础上,还提供了更丰富的功能。它们拥有初等模板所不拥有的额外属性:额外支持适合指针/整型类型的原子算术运算,例如 fetch_add 、 fetch_sub等。
以下整数类型之一实例化时, std::atomic
提供适合于整数类型的额外原子操作,例如 fetch_add
、 fetch_sub
、 fetch_and
、 fetch_or
、 fetch_xor
:
<cstdint>
中的 typedef 所需的额外整数类型。类型定义
template <> struct atomic<integral> {
atomic() = default;
constexpr atomic(integral);
atomic(const atomic&) = delete;
bool is_lock_free() const volatile;
bool is_lock_free() const;
void store(integral, memory_order = memory_order_seq_cst) volatile;
void store(integral, memory_order = memory_order_seq_cst);
integral load(memory_order = memory_order_seq_cst) const volatile;
integral load(memory_order = memory_order_seq_cst) const;
operator integral() const volatile;
operator integral() const;
integral exchange(integral, memory_order = memory_order_seq_cst) volatile;
integral exchange(integral, memory_order = memory_order_seq_cst);
bool compare_exchange_weak(integral&, integral, memory_order, memory_order) volatile;
bool compare_exchange_weak(integral&, integral, memory_order, memory_order);
bool compare_exchange_strong(integral&, integral, memory_order, memory_order) volatile;
bool compare_exchange_strong(integral&, integral, memory_order, memory_order);
bool compare_exchange_weak(integral&, integral, memory_order = memory_order_seq_cst) volatile;
bool compare_exchange_weak(integral&, integral, memory_order = memory_order_seq_cst);
bool compare_exchange_strong(integral&, integral, memory_order = memory_order_seq_cst) volatile;
bool compare_exchange_strong(integral&, integral, memory_order = memory_order_seq_cst);
//原子对象的封装值加 val,并返回原子对象的旧值。整个过程是原子的
integral fetch_add(integral, memory_order = memory_order_seq_cst) volatile;
integral fetch_add(integral, memory_order = memory_order_seq_cst);
//原子对象的封装值减 val,并返回原子对象的旧值。整个过程是原子的
integral fetch_sub(integral, memory_order = memory_order_seq_cst) volatile;
integral fetch_sub(integral, memory_order = memory_order_seq_cst);
//原子地进行参数和原子对象的值的逐位与,并获得先前保有的值
integral fetch_and(integral, memory_order = memory_order_seq_cst) volatile;
integral fetch_and(integral, memory_order = memory_order_seq_cst);
// 原子地进行参数和原子对象的值的逐位或,并获得先前保有的值
integral fetch_or(integral, memory_order = memory_order_seq_cst) volatile;
integral fetch_or(integral, memory_order = memory_order_seq_cst);
// 原子地进行参数和原子对象的值的逐位异或,并获得先前保有的值
integral fetch_xor(integral, memory_order = memory_order_seq_cst) volatile;
integral fetch_xor(integral, memory_order = memory_order_seq_cst);
atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;
integral operator=(integral) volatile;
integral operator=(integral);
// 令原子值增加或减少一
integral operator++(int) volatile; //后缀++
integral operator++(int);
integral operator--(int) volatile; /后缀--
integral operator--(int);
integral operator++() volatile; //前缀++
integral operator++();
integral operator--() volatile; //前缀--
integral operator--();
// 加、减,或与原子值进行逐位与、或、异或
integral operator+=(integral) volatile;
integral operator+=(integral);
integral operator-=(integral) volatile;
integral operator-=(integral);
integral operator&=(integral) volatile;
integral operator&=(integral);
integral operator|=(integral) volatile;
integral operator|=(integral);
integral operator^=(integral) volatile;
integral operator^=(integral);
};
类型定义
template <class T> struct atomic<T*> {
atomic() = default;
constexpr atomic(T*);
atomic(const atomic&) = delete;
atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;
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);
// 上面的接口我们上一篇文章已经介绍过,这里不再介绍了
T* fetch_add(ptrdiff_t, memory_order = memory_order_seq_cst) volatile;
T* fetch_add(ptrdiff_t, memory_order = memory_order_seq_cst);
T* fetch_sub(ptrdiff_t, memory_order = memory_order_seq_cst) volatile;
T* fetch_sub(ptrdiff_t, memory_order = memory_order_seq_cst);
T* operator=(T*) volatile;
T* operator=(T*);
T* operator++(int) volatile;
T* operator++(int);
T* operator--(int) volatile;
T* operator--(int);
T* operator++() volatile;
T* operator++();
T* operator--() volatile;
T* operator--();
T* operator+=(ptrdiff_t) volatile;
T* operator+=(ptrdiff_t);
T* operator-=(ptrdiff_t) volatile;
T* operator-=(ptrdiff_t);
};
仅为 atomic<Integral>
模板特化的成员
T fetch_add (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
T fetch_add (T val, memory_order sync = memory_order_seq_cst) noexcept;
仅为 atomic<T*>
模板特化的成员
T fetch_add (ptrdiff_t val, memory_order sync = memory_order_seq_cst) volatile noexcept;
T fetch_add (ptrdiff_t val, memory_order sync = memory_order_seq_cst) noexcept;
原子地以值和 val
的算术加法结果替换当前值。运算是读修改写操作。按照 order
的值影响内存。
对于有符号 Integral
类型,定义算术为使用补码。对于 T*
类型,结果可能为未定义的地址,但此外运算无未定义行为。
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<long long> data;
void do_work()
{
data.fetch_add(1, std::memory_order_relaxed);
}
int main()
{
std::thread th1(do_work);
std::thread th2(do_work);
std::thread th3(do_work);
std::thread th4(do_work);
std::thread th5(do_work);
th1.join();
th2.join();
th3.join();
th4.join();
th5.join();
std::cout << "Result:" << data << '\n';
}
// T is integral
T fetch_sub(T v, memory_order m = memory_order_seq_cst) volatile noexcept;
T fetch_sub(T v, memory_order m = memory_order_seq_cst) noexcept;
// T is pointer
T fetch_sub(ptrdiff_t v, memory_order m = memory_order_seq_cst) volatile noexcept;
T fetch_sub(ptrdiff_t v, memory_order m = memory_order_seq_cst) noexcept;
将原子对象的封装值减 val,并返回原子对象的旧值,适用于整形和指针类型的 std::atomic 特化版本,整个过程是原子的。其功能用伪代码表示为:
auto old = contained
contained -= v
return old
自增运算符重载,有两种形式,第一种形式 (1) 返回自增后的值(即前缀++),第二种形式(2) 返回自增前的值(即后缀++),适用于整形和指针类型的 std::atomic 特化版本。
自减运算符重载,有两种形式, 第一种形式 (1) 返回自减后的值(即前缀–),第二种形式(2) 返回自减前的值(即后缀–),适用于整形和指针类型的 std::atomic 特化版本。
不管是基于整数的特化,还是指针特化,atomic均支持这两种种操作。其用法与未封装时一样,此处就不一一列举其函数原型了。
位操作,将contained按指定方式进行位操作,并返回contained的旧值,仅用于整数的特化。
// T is only for integral
integral fetch_and(integral v, memory_order m = memory_order_seq_cst) volatile noexcept;
integral fetch_and(integral v, memory_order m = memory_order_seq_cst) noexcept;
integral fetch_or(integral v, memory_order m = memory_order_seq_cst) volatile noexcept;
integral fetch_or(integral v, memory_order m = memory_order_seq_cst) noexcept;
integral fetch_xor(integral v, memory_order m = memory_order_seq_cst) volatile noexcept;
integral fetch_xor(integral v, memory_order m = memory_order_seq_cst) noexcept;
以xor为例,其操作相当于:
auto old = contained
contained ^= v
return old
与相应的fetch_*
操作不同的是,operator操作返回的是新值,仅用于整数的特化。
// T is only for integral
T operator &=(T v) volatile noexcept {return fetch_and(v) & v;}
T operator &=(T v) noexcept {return fetch_and(v) & v;}
T operator |=(T v) volatile noexcept {return fetch_or(v) | v;}
T operator |=(T v) noexcept {return fetch_or(v) | v;}
T operator ^=(T v) volatile noexcept {return fetch_xor(v) ^ v;}
T operator ^=(T v) noexcept {return fetch_xor(v) ^ v;}
// T is for pointer
T* operator+=(ptrdiff_t) volatile;
T* operator+=(ptrdiff_t);
T* operator-=(ptrdiff_t) volatile;
T* operator-=(ptrdiff_t);
// T is for integral
integral operator+=(integral) volatile;
integral operator+=(integral);
integral operator-=(integral) volatile;
integral operator-=(integral);
以上各个 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 | 是 | 否 | 否 |
std::atomic 模板可用任何满足可复制构造 (CopyConstructible) 及可复制赋值 (CopyAssignable) 的可平凡复制 (TriviallyCopyable) 类型 T 特化。若下列任何值为 false 则T不可用于atomic模板:
那么到底什么是TriviallyCopyable类型呢?有以下三种:
Scalar types(标量类型)
标量类型(Scalar type)是相对复合类型(Compound type)来说的:标量类型只能有一个值,而复合类型可以包含多个值。复合类型是由标量类型构成的。
在C语言中,整数类型(int、short、long等)、字符类型(char、wchar_t等)、枚举类型(enum)、小数类型(float、double等)、布尔类型(bool)都属于标量类型,一份标量类型的数据只能包含一个值。
结构体(struct)、数组(array)、字符串(string)都属于复合类型,一份复合类型的数据可以包含多个标量类型的值,也可以包含其他复合类型的值。
rivially copyable classes(拷贝不变类),一个类型如果是trivially copyable,则使用memcpy这种方式把它的数据从一个地方拷贝出来会得到相同的结果。编译器如何判断一个类型是否trivially copyable呢?cpp标准把trivial类型定义如下,一个拷贝不变(trivially copyable)类型是指::
Arrays of TriviallyCopyable objects可平凡复制 (TriviallyCopyable) 对象的数组。
一个trivial class类型是指有一个trivial类型的默认构造函数,而且是拷贝不变的(trivially copyable)的class。特别注意,拷贝不变类型和trivial类型都不能有虚机制。那么trivial和non-trivial类型到底是什么呢?这里给出一个非官方、不严谨的判断方式,方便大家对trivially copyable有一个直观的认识。一个trivial copyable类在四个点上没有自定义动作,也没有编译器加入的额外动作(如虚指针初始化就属额外动作),这四个点是:
为了加深理解,我们来看一下下面的例子(所有的类都是trivial的):
// 空类
struct A1 {};
// 成员变量是trivial的
struct A2 {
int x;
};
// 基类是trivial的
struct A3 : A2 {
// 非用户自定义的构造函数(使用编译器提供的default构造)
A3() = default;
int y;
};
struct A4 {
int a;
private: // 对防问限定符没有要求,A4仍然是trivial的
int b;
};
struct A5 {
A1 a;
A2 b;
A3 c;
A4 d;
};
struct A6 {
// A2是trivial,它的array也是trivial
A2 a[16];
};
struct A7 {
A6 c;
void f(); // 普通成员函数是允许的
};
struct A8 {
int x;
// 对静态成员无要求(std::string是non-trivial的)
static std::string y;
};
struct A9 {
// 非用户自定义
A9() = default;
// 普通构造函数是可以的(前提是我们已经有了非定义的缺省构造函数)
A9(int x) : x(x) {};
int x;
};
而下面这些类型都是non-trivial的:
struct B {
// 有虚函数(编译器会隐式生成缺省构造,同时会初始化虚函数指针)
virtual f();
};
struct B2 {
// 用户自定义缺省构造函数
B2() : z(42) {}
int z;
};
struct B3 {
// 用户自定义缺省构造函数
B3();
int w;
};
// 虽然使用了default,但在缺省构造声明处未指定,因此被判断为non-trivial的
NonTrivial3::NonTrivial3() = default;
struct B4 {
// 虚析构是non-trivial的
virtual ~B4();
};
STL在其头文件<type_traits>中定义了对trivially copyable类型的检测:
template <typename T>
struct std::is_trivially_copyable;
判断类A是否trivially copyable:std::is_trivially_copyable<A>::value
,该值是一个const bool类型,如果为true则是trivially copyable的,否则不是。
参考