• C++原子变量


    1.简介

    C++11 提供了一个原子类型 std::atomic,通过这个原子类型管理的内部变量就可以称之为原子变量,我们可以给原子类型指定 bool、char、int、long、指针等类型作为模板参数(不支持浮点类型和复合类型)

    原子指的是一系列不可被 CPU 上下文交换的机器指令,这些指令组合在一起就形成了原子操作。在多核 CPU 下,当某个 CPU 核心开始运行原子操作时,会先暂停其它 CPU 内核对内存的操作,以保证原子操作不会被其它 CPU 内核所干扰。

    由于原子操作是通过指令提供的支持,因此它的性能相比锁和消息传递会好很多。相比较于锁而言,原子类型不需要开发者处理加锁和释放锁的问题,同时支持修改,读取等操作,还具备较高的并发性能,几乎所有的语言都支持原子类型。

    可以看出原子类型是无锁类型,但是无锁不代表无需等待,因为原子类型内部使用了 CAS 循环,当大量的冲突发生时,该等待还是得等待!但是总归比锁要好。

    • CAS 全称是 Compare and swap, 它通过一条指令读取指定的内存地址,然后判断其中的值是否等于给定的前置值,如果相等,则将其修改为新的值

    C++11 内置了整形的原子变量,这样就可以更方便的使用原子变量了。

    • 在多线程操作中,使用原子变量之后就不需要再使用互斥量来保护该变量了,用起来更简洁。因为对原子变量进行的操作只能是一个原子操作(atomic operation),原子操作指的是不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何的上下文切换。
    • 多线程同时访问共享资源造成数据混乱的原因就是因为 CPU 的上下文切换导致的,使用原子变量解决了这个问题,因此互斥锁的使用也就不再需要了。

    2.atomic 类成员

    类定义

    • 通过定义可得知:在使用这个模板类的时候,一定要指定模板类型。
    // 定义于头文件 <atomic>
    template< class T >
    struct atomic;
    
    • 1
    • 2
    • 3

    构造函数

    // ①
    atomic() noexcept = default;
    // ②
    constexpr atomic( T desired ) noexcept;
    // ③
    atomic( const atomic& ) = delete;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 构造函数①:默认无参构造函数。
    • 构造函数②:使用 desired 初始化原子变量的值。
    • 构造函数③:使用 =delete 显示删除拷贝构造函数,不允许进行对象之间的拷贝

    公共成员函数

    • 原子类型在类内部重载了 = 操作符,并且不允许在类的外部使用 = 进行对象的拷贝。
    T operator=( T desired ) noexcept;
    T operator=( T desired ) volatile noexcept;
    
    atomic& operator=( const atomic& ) = delete;
    atomic& operator=( const atomic& ) volatile = delete;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    原子地以 desired 替换当前值。按照 order 的值影响内存。

    • desired:存储到原子变量中的值
    • order:强制的内存顺序
    void store( T desired, std::memory_order order = std::memory_order_seq_cst ) noexcept;
    void store( T desired, std::memory_order order = std::memory_order_seq_cst ) volatile noexcept;
    
    • 1
    • 2

    原子地加载并返回原子变量的当前值。按照 order 的值影响内存。直接访问原子对象也可以得到原子变量的当前值。

    T load( std::memory_order order = std::memory_order_seq_cst ) const noexcept;
    T load( std::memory_order order = std::memory_order_seq_cst ) const volatile noexcept;
    
    • 1
    • 2

    特化成员函数

    • 复合赋值运算符重载,主要包含以下形式:
      在这里插入图片描述
    • 以上各个 operator 都会有对应的 fetch_* 操作,详细见下表:
      在这里插入图片描述

    内存顺序约束

    • 通过上面的 API 函数我们可以看出,在调用 atomic 类提供的 API 函数的时候,需要指定原子顺序,在 C++11 给我们提供的 API 中使用枚举用作执行原子操作的函数的实参,以指定如何同步不同线程上的其他操作。

    • 定义如下:

    typedef enum memory_order {
        memory_order_relaxed,   // relaxed
        memory_order_consume,   // consume
        memory_order_acquire,   // acquire
        memory_order_release,   // release
        memory_order_acq_rel,   // acquire/release
        memory_order_seq_cst    // sequentially consistent
    } memory_order;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • memory_order_relaxed, 这是最宽松的规则,它对编译器和 CPU 不做任何限制,可以乱序
    • memory_order_release 释放,设定内存屏障 (Memory barrier),保证它之前的操作永远在它之前,但是它后面的操作可能被重排到它前面
    • memory_order_acquire 获取 , 设定内存屏障,保证在它之后的访问永远在它之后,但是它之前的操作却有可能被重排到它后面,往往和 Release 在不同线程中联合使用
    • memory_order_consume:改进版的 memory_order_acquire ,开销更小
    • memory_order_acq_rel,它是 Acquire 和 Release 的结合,同时拥有它们俩提供的保证。比如你要对一个 atomic 自增 1,同时希望该操作之前和之后的读取或写入操作不会被重新排序
    • memory_order_seq_cst 顺序一致性, memory_order_seq_cst 就像是 memory_order_acq_rel 的加强版,它不管原子操作是属于读取还是写入的操作,只要某个线程有用到 memory_order_seq_cst 的原子操作,线程中该 memory_order_seq_cst 操作前的数据操作绝对不会被重新排在该 memory_order_seq_cst 操作之后,且该 memory_order_seq_cst 操作后的数据操作也绝对不会被重新排在 memory_order_seq_cst 操作前。

    C++20 新增成员

    • 在 C++20 版本中添加了新的功能函数,可以通过原子类型来阻塞线程,和条件变量中的等待 / 通知函数是一样的。
      在这里插入图片描述

    类型别名
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    3.原子变量的使用

    假设我们要制作一个多线程交替数数的计数器,我们使用互斥锁和原子变量的方式分别进行实现,对比一下二者的差异:

    互斥锁版本

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <atomic>
    #include <functional>
    using namespace std;
    
    struct Counter
    {
        void increment()
        {
            for (int i = 0; i < 10; ++i)
            {
                lock_guard<mutex> locker(m_mutex);
                m_value++;
                cout << "increment number: " << m_value
                    << ", theadID: " << this_thread::get_id() << endl;
                this_thread::sleep_for(chrono::milliseconds(100));
            }
        }
    
        void decrement()
        {
            for (int i = 0; i < 10; ++i)
            {
                lock_guard<mutex> locker(m_mutex);
                m_value--;
                cout << "decrement number: " << m_value
                    << ", theadID: " << this_thread::get_id() << endl;
                this_thread::sleep_for(chrono::milliseconds(100));
            }
        }
    
        int m_value = 0;
        mutex m_mutex;
    };
    
    int main()
    {
        Counter c;
        auto increment = bind(&Counter::increment, &c);
        auto decrement = bind(&Counter::decrement, &c);
        thread t1(increment);
        thread t2(decrement);
    
        t1.join();
        t2.join();
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 测试:
      在这里插入图片描述

    原子变量版本

    #include <iostream>
    #include <thread>
    #include <atomic>
    #include <functional>
    using namespace std;
    
    struct Counter
    {
        void increment()
        {
            for (int i = 0; i < 10; ++i)
            {
                m_value++;
                cout << "increment number: " << m_value
                    << ", theadID: " << this_thread::get_id() << endl;
                this_thread::sleep_for(chrono::milliseconds(500));
            }
        }
    
        void decrement()
        {
            for (int i = 0; i < 10; ++i)
            {
                m_value--;
                cout << "decrement number: " << m_value
                    << ", theadID: " << this_thread::get_id() << endl;
                this_thread::sleep_for(chrono::milliseconds(500));
            }
        }
        // atomic<int> == atomic_int
        atomic_int m_value = 0;
    };
    
    int main()
    {
        Counter c;
        auto increment = bind(&Counter::increment, &c);
        auto decrement = bind(&Counter::decrement, &c);
        thread t1(increment);
        thread t2(decrement);
    
        t1.join();
        t2.join();
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 测试:
      在这里插入图片描述

    通过代码的对比可以看出,使用了原子变量之后,就不需要再定义互斥量了,在使用上更加简便,并且这两种方式都能保证在多线程操作过程中数据的正确性,不会出现数据的混乱。

    原子类型 atomic 可以封装原始数据最终得到一个原子变量对象,操作原子对象能够得到和操作原始数据一样的效果,当然也可以通过 store() 和 load() 来读写原子对象内部的原始数据。

  • 相关阅读:
    nuxt.js服务端渲染项目性能优化总结
    五大步骤实现MapGIS Web 功能服务拓展
    etcdctl-管理操作etcd集群
    二、安全与风险管理—安全与风险管理基础
    pacman 升级软件包提示 “failed to commit transaction (invalid or corrupted package)“
    c++面向对象基础编程——运算符重载
    【餐厅点餐平台|四】UI设计+效果展示
    【最佳实践】高可用mongodb集群(1分片+3副本):规划及部署
    70个必备的数据分析工具
    计算机毕业设计JavaWeb医学院校大学生就业信息管理系统(源码+系统+mysql数据库+lw文档)
  • 原文地址:https://blog.csdn.net/u011436427/article/details/125435746