原子变量
- 原子变量的解释
- 原子变量类 atomic
- 禁止拷贝
- 赋值和读取操作
- 通过函数
- 特化类型
- 使用
- 初始化
- 赋值
- 读取
- 操作对象(指针访问)
- 使用原子变量解决乱序问题
- 内存循序约束
原子变量的解释
原子作为长期最小的单位,在这里被定义为不可分割的最小操作,原子性也别定义为不可再分的操作,在C\C++ 中由于程序执行的非常的快,指令可能出现乱序执行的情况(不能保证指令执行的循序和编写程序循序的一致),有编译器的优化,有执行上的优化,总之是为了让程序执行更快导致的特性,这在单线程中几乎不会出现什么问题可以让程序飞快的执行,但是到了多线程的时代就会有问题,实际上就是线程同步上的问题,于是C++11(Java 在2004 年的java5就支持了C++晚了非常久)增加原子变量来实现原子操作。
原子变量类 atomic
原子变量类 可以管理 一个int 变量或者一个指针变量 实现对他的原子操作,注意只能支持 管理非浮点型 变量 或者指针变量
原子变量有以下特性
禁止拷贝
根据原子变量内部的定义
atomic() = default;~atomic() noexcept = default;atomic(const atomic&) = delete;atomic& operator=(const atomic&) = delete;atomic& operator=(const atomic&) volatile = delete;
可以知道原子变量是不能被拷贝的
赋值和读取操作
通过函数
void store(值,内存顺序约束); //写入一个值
T load(内存顺序约束);//读取值
T exchange(值,内存顺序约束); //写入并且返回原来的值
fetch_add() 相当于 operator +
fetch_sub ()相当于 operator -
fetch_and ()相当于 operator & 不支持指针变量
fetch_or ()相当于 operator | 不支持指针变量
fetch_xor()相当于 operator ~ 不支持指针变量
函数名 | 运算符 | 是否支持指针变量 |
---|---|---|
fetch_add() | operator + | 支持 |
fetch_sub () | operator - | 支持 |
fetch_and () | operator& | 不支持 |
fetch_or () | operator | | 不支持 |
fetch_xor() | operator ~ | 不支持 |
特化类型
C++给常用的类型进行一些typedef减少编写定义的长度如:
特化类型 | 原始定义 |
---|---|
atomic_int | std::atmic<int> |
atomic_bool | std::atmic<bool> |
atomic_uint | std::atmic<uint> |
atomic_long | std::atmic<long> |
atomic_char | std::atmic<char> |
…还有非常多可以根据使用的时候根据前缀让IDE补全
使用
初始化
int main(void)
{atomic<int> a(1); //通过构造函数初始化atomic<int >b;b = 1; //通过重载运算符进行初始化, 不建议因为一开始没有初始化内部是未定义状态atomic<int >c;atomic_init(&c,3);//使用atomic_init进行初始化}
赋值
int main(void)
{atomic<int> a(1); //通过构造函数初始化a.store(123); //直接赋值int t = a.exchange(333);//交换旧值cout<<t<<endl;
}
读取
int main(void)
{atomic<int> a(1); //通过构造函数初始化a.store(123);cout<<a.load()<<endl;
}
操作对象(指针访问)
#include <queue>
#include <condition_variable>
#include <atomic>
using namespace std;
using namespace chrono;class A
{
public:A(){a =1; }inline int get(){return a;}void set(int _a){a = _a;}
private:int a;};
int main(void)
{atomic<A *> a(new A()); //通过构造函数初始化auto t = a.load(); //获取对象t->set(1);t->get();auto t2 = a.exchange(new A());// 重新设置delete t2;auto t3 = a.load();a.store(new A());delete t3;delete a.load();}
使用原子变量解决乱序问题
一个简单的例子,这里最终a的结果不一定是 2000000 考虑编译器的优化以及乱序执行 要么直接等于1000000(编译器检测到只做加法直接优化成等于1000000)要么是一个小于2000000的值(也有一定概率等于2000000)
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <atomic>
using namespace std;
using namespace chrono;class A
{
public:A(){a = 0;}void thread1(){for(int i = 0;i<1000000;i++){++a;}}void thread2(){for(int i = 0;i<1000000;i++){++a;}}int a;};
int main(void)
{A a;thread thread1(&A::thread1,&a);thread thread2(&A::thread2,&a);thread1.join();thread2.join();cout<<a.a <<endl;
}
使用原子变量对他优化
atomic<int> a;
再次运行就会稳定输出 2000000
内存循序约束
前面介绍API的时候有些到这个参数,但是一直没有使用,原因是API中默认有一个参数 就是memory_order_seq_cst 保证内存序列一致
类型 | 作用 |
---|---|
memory_order_relaxed | 不做任何限制 |
memory_order_release | 保证写入内存之前的操作在它的执行前是完成的(不保证后面的会不会在它前面) |
memory_order_acquire | 保证读取数据之后的的操作在读取他之后是完成的(不保证他前面的会不会在它后面) |
memory_order_consume | 类似memory_order_acquire 当数据发布会收到订阅所以效率稍高 |
memory_order_acq_rel | 保证读写内存一致性 也就是 memory_order_release 和memory_order_acquire 结合 |
memory_order_seq_cst | 保证顺序的一致性保证前后操作的循序均一致 |
对 memory_order_consume的补充
这里有一个例子 consumer 函数在等待赋值此时能保证他里面的内容一直是“Hello” 但是不能保证Data的数据一直42
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
std::atomic<std::string*> ptr;
int data;
std::atomic<int> atomic_a;
void producer()
{std::string* p = new std::string("Hello"); //Adata = 42;//Bptr.store(p, std::memory_order_release); //C
}void consumer()
{std::string* p2;while (!(p2 = ptr.load(std::memory_order_consume))) //D;assert(*p2 == "Hello"); // E never fires: *p2 carries dependency from ptrassert(data == 42); // F may or may not fire: data does not carry dependency from ptr
}int main()
{std::thread t1(producer);std::thread t2(consumer);t1.join(); t2.join();
}