当前位置: 首页 > news >正文

C++ 11原子变量

原子变量

  • 原子变量的解释
  • 原子变量类 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_intstd::atmic<int>
atomic_boolstd::atmic<bool>
atomic_uintstd::atmic<uint>
atomic_longstd::atmic<long>
atomic_charstd::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");  //A
    data = 42;//B
    ptr.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 ptr
    assert(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();
}

相关文章:

  • snort3.0 获取注册规则(19000多条)
  • Flink怎么保证Exactly - Once 语义
  • 传统架构 VS 云上架构
  • 自定义解的使用,反射,代理模式
  • 国自然地区基金|影像组学联合病理组学预测进展期胃癌术后预后的研究|基金申请·25-02-13
  • 网络编程(tcp线程池)
  • Springboot集成Milvus和Embedding服务,实现向量化检索
  • 3天功能开发→3小时:通义灵码2.0+DEEPSEEK实测报告,单元测试生成准确率92%的秘密
  • DeepSeek+kimi自动生成ppt
  • Pythong 解决Pycharm 运行太慢
  • Docker容器基本操作
  • maven web项目如何定义filter
  • Unity嵌入到Winform
  • 【AI论文】10亿参数大语言模型能超越405亿参数大语言模型吗?重新思考测试时计算最优缩放
  • 使用Python爬虫实时监控行业新闻案例
  • 基于 STM32 的智能电动车防盗与管理系统
  • C++ 设计模式-适配器模式
  • RSS远小于容器内存限制,却收到了内存告警
  • 20250213 随笔 PV(Page View) 和 UV(Unique Visitor)
  • Hutool - BloomFilter:便捷的布隆过滤器实现
  • 脑血管支架:救命神器还是定时炸弹?听听医生的大实话
  • 真人秀《幸存者》百万美元奖金,25年间“缩水”近一半
  • 外交部就习近平主席将出席中拉论坛第四届部长级会议开幕式介绍情况
  • 著名学者黄修己去世,享年90岁
  • 韩国执政党总统候选人更换方案被否决,金文洙候选人资格即刻恢复
  • 气象干旱黄色预警继续:陕西西南部、河南西南部等地特旱