首页 > 其他分享 >整型变量的原子操作

整型变量的原子操作

时间:2024-03-14 17:59:36浏览次数:19  
标签:std 变量 int 原子 整型 操作 include order

什么是原子操作

原子操作(Atomic Operation)是指不可中断的操作,即在多线程环境下,当一个线程在执行原子操作时,不会被其他线程的调度和中断所影响。这种操作在多线程编程中尤为重要,因为它能保证操作的原子性,从而避免数据竞争和不一致。

原子操作的特性

  1. 原子性:操作不可分割,即不可中断。
  2. 可见性:操作完成后,其他线程能立即看到结果。
  3. 有序性:编译器和处理器不会重排序原子操作。

c++ 原子操作的支持

在C++中,原子操作可以通过<atomic>库来实现。<atomic>库提供了一组模板类,如std::atomic<T>,其中T可以是整型、指针类型等。这些模板类提供了一系列成员函数,如load()store()exchange()compare_exchange_weak()compare_exchange_strong()等,以实现原子操作。

示例使用不适用原子操作和使用原子操作比对

示例我们创建二十个线程,同时分别对同一个对象的成员变量(m_aa(初值为0))做10000自增运算,按照正常运算,所有线程运行完成后,对象的成员变量值应该是20*10000 = 200000.

不使用原子操作

#include<iostream>
#include<thread>
#include<vector>
#include<atomic>
class ThreadTsetAtomic
{
private:
    int m_aa;
    // std::atomic<int> m_aa;
public:
    void add()
    {   
        int b=10000;
        while(b--)
        {
            m_aa++;
        }
        
    }
    ThreadTsetAtomic(const int a):m_aa(a)
    {}
    void showValue()
    {
        std::cout<<m_aa<<std::endl;
    }
};

void testFun(ThreadTsetAtomic* sub)
{
    sub->add();
}
int main()
{   
    ThreadTsetAtomic test1(0);
    std::vector<std::thread> threadVec;
    for(int i=0;i<20;i++)
    {   
        std::thread test(testFun,&test1);
        threadVec.push_back(std::move(test));
        // threadVec.emplace_back(testFun,test1);
    }
    for (auto& t : threadVec) 
    {  
        t.join();  
    }  
    test1.showValue();
    return 0;
}

编译运行

可以看到这里运行的结果是30799 和我们实际预期的200000值相差很大

使用原子操作

示例

#include<iostream>
#include<thread>
#include<vector>
#include<atomic>
class ThreadTsetAtomic
{
private:
    // int m_aa;
    std::atomic<int> m_aa; //原子整型变量m_aa
public:
    void add()
    {   
        int b=10000;
        while(b--)
        {
            m_aa++;
        }
        
    }
    ThreadTsetAtomic(const int a):m_aa(a)
    {}
    void showValue()
    {
        std::cout<<m_aa<<std::endl;
    }
};

void testFun(ThreadTsetAtomic* sub)
{
    sub->add();
}
int main()
{   
    ThreadTsetAtomic test1(0);
    std::vector<std::thread> threadVec;
    for(int i=0;i<20;i++)
    {   
        std::thread test(testFun,&test1);
        threadVec.push_back(std::move(test));
        // threadVec.emplace_back(testFun,test1);
    }
    for (auto& t : threadVec) 
    {  
        t.join();  
    }  
    test1.showValue();
    return 0;
}

编译运行

运行结果为200000和我们预期的结果一致

为什么示例一中不使用原子操作运行的结果和我们预期的值相差这么大呢?

示例

#include<iostream>

int main()
{
    int a =0;
    a++;
    return 0;
}

编译运行

我们查看想加的汇编代码

0x0000555555554745 <+11>:    addl   $0x1,-0x4(%rbp) 这一句的实现

  1. 地址计算: 首先,CPU 将寄存器 %rbp 的值与偏移量 -0x4 相加,得到内存地址 -0x4(%rbp)

  2. 内存访问: CPU 访问计算得到的内存地址,读取其中的值。这个值是存储在该内存位置中的数据,可能是一个整数值。

  3. 加法操作: CPU 将从内存中读取的值与立即数 0x1 相加,得到一个新的结果。

  4. 写回内存: 最后,CPU 将加法结果写回到内存地址 -0x4(%rbp) 所指向的内存位置中。这会覆盖原来的值,更新为新的结果。

也就是这个过程并非原子操作,因为涉及多个步骤,其中可能会发生中断、上下文切换或其他并发操作。要确保该操作是原子的,可能需要使用硬件支持的原子操作指令或锁来确保在多线程环境下的原子性。

结合上面示例不加原子操作分析,也就是多线程运行时,整形变量的自加不是原子操作的,当一个线程的操作还未完成可能这时候cpu就进行了线程切换,从而导致计数值不准。

补充

std::atomic  API

  1. 加载(Load)和存储(Store):

  • T load(std::memory_order order = std::memory_order_seq_cst) const noexcept;
  • void store(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept;

这对函数允许加载和存储原子变量的值。load 函数会返回当前原子变量的值,而 store 函数会将给定的值存储到原子变量中。

#include <atomic>
#include <iostream>

std::atomic<int> value(0);

int main() {
    value.store(10); // 存储值为 10 到原子变量
    int loaded_value = value.load(); // 加载原子变量的值
    std::cout << "Loaded value: " << loaded_value << std::endl;
    return 0;
}
  1. 交换(Exchange):

  • T exchange(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept;

这个函数会原子地将给定的值存储到原子变量中,并返回原子变量之前的值。

#include <atomic>
#include <iostream>

std::atomic<int> value(0);

int main() {
    int previous_value = value.exchange(10); // 原子地将值 10 存储到原子变量,并返回之前的值
    std::cout << "Previous value: " << previous_value << std::endl;
    return 0;
}
  1. 比较并交换(Compare and Exchange):

  • bool compare_exchange_weak(T& expected, T desired, std::memory_order success, std::memory_order failure) noexcept;
  • bool compare_exchange_strong(T& expected, T desired, std::memory_order success, std::memory_order failure) noexcept;

这对函数尝试原子地将原子变量的值与期望值进行比较,如果相等,则将新值存储到原子变量中,并返回 true;否则,返回 false。compare_exchange_weakcompare_exchange_strong 的区别在于当原子变量的值与期望值不同时,compare_exchange_weak 可能会失败,而 compare_exchange_strong 会循环直到操作成功。

#include <atomic>
#include <iostream>

std::atomic<int> value(0);

int main() {
    int expected = 0;
    int desired = 10;
    bool success = value.compare_exchange_weak(expected, desired); // 尝试将值从 0 替换为 10
    if (success) {
        std::cout << "Exchange successful" << std::endl;
    } else {
        std::cout << "Exchange failed" << std::endl;
    }
    return 0;
}

标签:std,变量,int,原子,整型,操作,include,order
From: https://blog.csdn.net/weixin_40026739/article/details/136714834

相关文章

  • 线程工具类与原子类
    参考文档:CountDownLatch、CyclicBarrier、Semaphore的用法和区别juc15_基本AtomicInteger、数组、引用AtomicStampedReference、对象的属性修改原子类AtomicIntegerFieldUp、原子操作增强类LongAdder辅助工具类CountDownLatch(闭锁)做减法允许一个或多个线程等待直到......
  • 并发支持库:条件变量
    互斥std::mutex(c++11)作用:互斥锁,提供一种原子操作,保护共享数据被多个线程访问的安全性#include<mutex>std::mutexmutex;{std::lock_guard<std::mutex>lock(mutex);//operatedata};成员函数lock锁定互斥,若互斥不可用则阻塞try_lock尝试锁定互斥,若......
  • 并发支持库:原子操作类型
    原子操作这些组件为细粒度的原子操作提供,允许无锁并发编程。类型别名atomic_bool(C++11)std::atomic(typedef)atomic_char(C++11)std::atomic(typedef)atomic_schar(C++11)std::atomic(typedef)atomic_uchar(C++11)std::atomic(typedef)atomic_short(......
  • volatile关键字是如何确保多线程环境下变量的可见性和有序性
    VOLATILE关键字在JAVA中用于确保多线程环境下的变量可见性和一定程度的有序性,其具体实现机制基于JAVA内存模型(JAVAMEMORYMODEL,JMM):可见性:当一个线程修改了标记为volatile的共享变量时,它会强制将这个变量值从当前线程的工作内存刷新回主内存。同时,其他线程在读取该volatil......
  • 滴水逆向笔记系列-c语言总结2-10.变量-11.if逆向-12.正向基础
    第十课c语言31.编码ASCII标准的ASCII编码只需要七位,第八位在拓展ASCII编码使用GB23122.局部变量和全局变量下面代码输出结果为1111(x=11改变了全局的x)第十一课c语言41.内存图2.逆向参数个数3.简单逆向if代码4.if...else...反汇编判断跳转执行一部分代......
  • Serializer 序列化 -----视图层传入一个变量到序列化器的方法
    fromrest_frameworkimportserializersclassMyModelSerializer(serializers.ModelSerializer):classMeta:model=MyModelfields=['field1','field2']defto_representation(self,instance):......
  • MeterSphere接口自动化系列之动态设置全局变量
    一、问题描述:    所有场景接口参数中需要token,token是由登录生成,每次登录后token会改变,该如何动态获取token并提供给后续场景使用?二、问题分析:    针对该问题,需要考虑,每个场景执行前登录一次,还是所有场景执行前只登录一次。    基于上述的分析,......
  • SQL 必须声明标量变量
    原文链接:https://deepinout.com/sql/sql-questions/17_sql_must_declare_the_scalar_variable.html什么是标量变量?在SQL中,标量变量是用来存储和表示单个数据值的容器。这些数据值可以是数字、字符串、日期等。标量变量可以在SQL查询和存储过程中使用,可以进行赋值和获取值的操作......
  • python环境变量问题备忘
    写了一个py脚本,手动在容器里能执行,但是配置了定时任务怎么都不执行,但是其他shell、js脚本却都能执行,没办法,既然shell能执行,然后就写了一个shell脚本间接调用py脚本,好了,现在到是能执行了,没想竟然执行报错了“YoumayneedtoaddPYTHONIOENCODING=utf-8toyourenvironment”......
  • Java 引用变量的比较
    在Java中,当你使用双引号直接创建字符串时,如:Strings=“LXHYouth”;Strings2=“LXHYouth”;使用==运算符比较这两个引用时,结果为true然而,当你使用new关键字创建字符串对象时,情况就有所不同了:Strings3=newString(“LXHYouth”);//使用new关键字,s3指向堆中的一......