【C++11并发】Atomic 笔记

简介

用atomic定义的变量,支持原子操作,即要么全部完成操作,要不全部没有完成,我们是不可能看到中间状态。一般在多线程程序中,可以用atomic来完成数据同步。
标准库为我们主要提供了四类工具

  1. atomic类模板
  2. 操作atomic的全局方法
  3. atomic_flag
  4. 内存顺序,即约束了当前atomic对象前后代码直行的相对顺序

atomic_flag是保证无锁的,任何平台都可以放心使用;atomic 对于整型,浮点类型,提供的方法会略有不同;内存顺序是在操作原子对象的时候,可以施加影响

atomic_flag

atomic_flag相当于是一个原子的bool类型,与atomic的不同点,除了无锁之外,atomic_flag提供的方法也是比较有限的,没有load和store方法:
在这里插入图片描述

构造了方法,atomic_flag有默认构造方法,但是直到C++20,默认构造时才会将状态置为false,在此之前为定义状态。所以在C++20之前都是这么定义atomic_flag的

std::atomic_flag automatic_flag = ATOMIC_FLAG_INIT;    // false

test_and_set 将atomic_flag设置true,并返回之前的值,可能是true,也可能是false

bool test_and_set( std::memory_order order = std::memory_order_seq_cst ) volatile noexcept;    // order是内存顺序,后面会写
bool test_and_set( std::memory_order order = std::memory_order_seq_cst ) noexcept;

用atomic_flag实现自旋锁是一个经典用法

#include <atomic>
 
class Spinlock
{
public:
    spinlock_mutex():m_flag(ATOMIC_FLAG_INIT) {}
    void lock() {
        while(flag.test_and_set());    // 不断重试,直到test_and_set返回false。测试m_flag
    }
    void unlock() {
        flag.clear(std::memory_order_release);
    }
private:
    std::atomic_flag m_flag;
};

std::atomic

atomic对于不同的类型支持的方法不一样,任何类型都有的方法如下:
在这里插入图片描述
构造方法

atomic() noexcept = default;
constexpr atomic( T desired ) noexcept;    // 将atomic初值设置为desired,**但这个过程不是原子的**
atomic( const atomic& ) = delete;

atomic的拷贝赋值操作和普通类的不一样,他的返回值不是引用,而是拷贝。如果返回了引用就无法保证原则操作了。另外,等号右边的是T类型,而不是atomic类型。

T operator=( T desired ) noexcept;
T operator=( T desired ) volatile noexcept;
atomic& operator=( const atomic& ) = delete;
atomic& operator=( const atomic& ) volatile = delete;

检查atomic的当前实现是否为无锁方式。如果是,则atomic原子操作是cpu指令级,否则就是通过锁实现的原子操作,可能用的就是:std::mutex

bool is_lock_free() const noexcept;
bool is_lock_free() const volatile noexcept;

向atomic中写值,为原子操作

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;

从atomic中读取值,为原子操作。返回值为atomic中保存值的拷贝

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;

隐式转换,等价于load()

operator T() const noexcept;
operator T() const volatile noexcept;

读改写当前变量,返回值是读到的之前的值,改是指对atomic中管理值的修改(瞎猜的:会不会是写缓存,后面研究一下 ),写是指写内存。

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

比较修改当前变量
如果*this == expected, *this = desired,return true
否则 expected = *this, return false

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;

weak版本的CAS允许偶然出乎意料的返回(比如在字段值和期待值一样的时候却返回了false),不过在一些循环算法中,这是可以接受的。通常它比起strong有更高的性能。

atomic对整型支持的最好的,提供的方法也是最多的,还提供了很多typedef的别名,类似这样
在这里插入图片描述
对于整型atomic提供的额外方法有:
在这里插入图片描述
加法会返回之前的值

T fetch_add( T arg, std::memory_order order = std::memory_order_seq_cst ) noexcept;

加法也会返回之前的值

T fetch_sub( T arg, std::memory_order order = std::memory_order_seq_cst ) noexcept;

按位与,或,异或,也都会返回之前的值

如果atomic的类型为T*,那么直行加,减方法的时候,会返回T*

如果使用自定义类型去作为atomic的模板参数,需要这个自定义类型是可平凡复制的(TriviallyCopyable ),即下面对象的value都必须为true

std::is_trivially_copyable<T>::value
std::is_copy_constructible<T>::value
std::is_move_constructible<T>::value
std::is_copy_assignable<T>::value
std::is_move_assignable<T>::value

简单来说,可平凡复制类型表示这个类型的对象可以直接进行内存拷贝,而不需要执行特殊的拷贝构造函数或者析构函数。换句话说,可平凡复制类型的对象可以拷贝到char或者unsigned char数组中,可以通过std::memcpy or std::memmove操作。一个可平凡复制类型通常具有简单的内存布局,没有虚拟函数或者虚拟基类,并且其拷贝构造函数和析构函数是默认生成的。这意味着这种类型的对象可以更高效地进行拷贝和赋值操作。像标量类型(算术类型/枚举/指针),标量类型作为成员变量构成的类,以及相应数组和cv-qualified修饰的,都是平凡复制类型。
这里有一些例子:https://www.cnblogs.com/BuzzWeek/p/17578402.html

cv-qualified
CV分别是const 以及volatile的缩写。
用const,volatile以及const volatile之一作修饰的变量被成为cv-qualified ,否则该变量是cv-unqualified

内存顺序 std::memory_order

在atomic的很多方法中都有一个参数为std::memory_order类型,不过一般都有默认值(memory_order_seq_cst)。现代C++编译器为了进一步提高代码运行效率,在编译的时候,可能会改变代码的直行顺序,当然是在不改变原有代码逻辑的情况下。比如下面例子,不管那种顺序直行代码,都不影响最终结果。但改变顺序后,可能直行效率会更高一些。std::memory_order正是用来告诉编译器,要不要做这种优化,当然C++中定义会更加详细一些。

int a = 1;
int b = 2;
int c = a;
---
int b = 2;
int a = 1;
int c = a;

std::memory_order的定义如下

typedef enum memory_order {
    memory_order_relaxed,
    memory_order_consume,
    memory_order_acquire,
    memory_order_release,
    memory_order_acq_rel,
    memory_order_seq_cst
} memory_order;
枚举值含义
memory_order_relaxed允许编译器为了效率可以任意优化执行顺序
memory_order_consume如果后续有关于当前原子变量的操作,必须在本条原子操作完成之后直行
memory_order_acquire所有后续的读操作必须在本条原子操作完成后直行
memory_order_release所有之前的写操作完成后才能执行本条原子操作
memory_order_acq_relmemory_order_acquire 和 memory_order_release 的组合
memory_order_seq_cst全部存取都按顺序执行,不允许编译器优化直行顺序

比如使用 memory_order_consume

atomic<int*> ptr;
atomic<int> data;

{
	int* p2;
	while(!(p2=ptr.load(memory_order_consume)));
	assert(*p2=="Hello");    // 这一行一定在while之后
	int d = 5;    // 这一行可能在int* 2之前直行,也可能在while之前,总之由于他不依赖ptr,所以就有可能被编译器优化执行顺序
}

再比如 memory_order_acquire

atomic<int> a;
atomic<int> b;

{
	while(b.load(memory_order_acquire)!=2);//本原子操作必须完成才能执行之后所有的读原子操作, 即a.load一定在本条语句之后
	std::cout << a.load(memory_order_relaxed) << std::endl;
}

其他的基本类似

前面提到std::memory_order一般是作为atomic相关的方法的一个参数。那么如果想脱离atomic使用std::memory_order,C++提供了std::atomic_thread_fence

extern "C" void atomic_thread_fence( std::memory_order order ) noexcept;

一个官方的例子

// Global
std::string computation(int);
void print(std::string);
 
std::atomic<int> arr[3] = {-1, -1, -1};
std::string data[1000]; //non-atomic data
 
// Thread A, compute 3 values.
void ThreadA(int v0, int v1, int v2)
{
//  assert(0 <= v0, v1, v2 < 1000);
    data[v0] = computation(v0);
    data[v1] = computation(v1);
    data[v2] = computation(v2);
    std::atomic_thread_fence(std::memory_order_release);
    std::atomic_store_explicit(&arr[0], v0, std::memory_order_relaxed);
    std::atomic_store_explicit(&arr[1], v1, std::memory_order_relaxed);
    std::atomic_store_explicit(&arr[2], v2, std::memory_order_relaxed);
}
 
// Thread B, prints between 0 and 3 values already computed.
void ThreadB()
{
    int v0 = std::atomic_load_explicit(&arr[0], std::memory_order_relaxed);
    int v1 = std::atomic_load_explicit(&arr[1], std::memory_order_relaxed);
    int v2 = std::atomic_load_explicit(&arr[2], std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);
//  v0, v1, v2 might turn out to be -1, some or all of them.
//  Otherwise it is safe to read the non-atomic data because of the fences:
    if (v0 != -1)
        print(data[v0]);
    if (v1 != -1)
        print(data[v1]);
    if (v2 != -1)
        print(data[v2]);
}

操作atomic的全局方法

功能和atomic的成员方法一样,命名风格基本都是 atomic_xxx

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/235927.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在SpringData JPA 中实现对持久层的操作

1.导入依赖 hibernate 这个依赖自带实现JPA接口 <dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><depen…

openGauss学习笔记-149 openGauss 数据库运维-备份与恢复-逻辑备份与恢复之gs_restore

文章目录 openGauss学习笔记-149 openGauss 数据库运维-备份与恢复-逻辑备份与恢复之gs_restore149.1 背景信息149.2 命令格式149.3 参数说明149.3.1 通用参数-V, –version149.3.2 导入参数 149.4 示例 openGauss学习笔记-149 openGauss 数据库运维-备份与恢复-逻辑备份与恢复…

JVM虚拟机系统性学习-运行时数据区(方法区、程序计数器、直接内存)

方法区 方法区本质上是 Java 编译后代码的存储区域&#xff0c;存储了每一个类的结构信息&#xff0c;如&#xff1a;运行时常量池、成员变量、方法、构造方法和普通方法的字节码指令等内容 方法区主要存储的数据如下&#xff1a; Class 类型信息&#xff0c;如该 Class 为 …

conda 计算当前包的个数

Conda是一个强大的包管理器和环境管理器&#xff0c;它用于安装和管理来自不同源的软件包。若要计算当前conda环境中安装的包的数量&#xff0c;你可以使用以下命令&#xff1a; 首先&#xff0c;激活你想要检查的conda环境&#xff08;如果不是默认的base环境&#xff09;&am…

2023-2024年华为ICT网络赛道模拟题库

2023-2024年网络赛道模拟题库上线啦&#xff0c;全面覆盖网络&#xff0c;安全&#xff0c;vlan考点&#xff0c;都是带有解析 参赛对象及要求&#xff1a; 参赛对象&#xff1a;现有华为ICT学院及未来有意愿成为华为ICT学院的本科及高职院校在校学生。 参赛要求&#xff1a…

【XR806开发板试用】编译FreeRTOS系统

编译FreeRTOS系统&#xff0c;测试串口输出。 一、下载源码 1.1、获取源码 下载源码: rootubuntu:/home# wget https://bbs.aw-ol.com/assets/uploads/files/1693988430633-xr806_sdk.tar.gz 解压文件 rootubuntu:/home# tar xvf 1693988430633-xr806_sdk.tar.gz 1.2、获取…

用Kotlin抓取微博数据并进行热度预测

闲来无事&#xff0c;逛逛微博&#xff0c;看着每条热度很高的博文趣事&#xff0c;心想能否通过爬虫抓取微博热度并进行趋势分析&#xff0c;说干就干&#xff0c;这里需要注意的问题我会一一标注。 爬虫ip信息的设置是在爬虫程序中进行的。爬虫ip信息可以帮助爬虫程序在访问…

Kafka事务是怎么实现的?Kafka事务消息原理详解

目录 一、Kafka事务性消息1.1 介绍Kafka事务性消息1.2 事务性消息的应用场景1.3 Kafka事务性消息的优势 二、Kafka事务性消息的使用2.1 配置Kafka以支持事务性消息生产者配置消费者配置 2.2 生产者&#xff1a;发送事务性消息创建Kafka生产者开始事务发送消息提交或中止事务 2.…

Maven项目引入本地jar

Maven项目引入本地jar 1.对应maven模块项目中建lib目录&#xff0c;将jar放入进去 2.在对应的模块pom.xml中引入此依赖jar 3.在对应的maven-plugin插件打包的pom.xml中指定需要includeSystemScope为true的jar

HTML的img常见应用属性

目录 一、src、alt、width、height 的运用二、title的运用三、align的运用四、border的运用 一、src、alt、width、height 的运用 src指定图像的URL&#xff0c;即图像的路径alt指定图像的替代文本&#xff0c;当图像无法显示时&#xff0c;会显示替代文本。width指定图像的宽…

【特殊文件(一)】属性文件读写实操

文章目录 属性文件特殊文件概述Properties属性文件概述Properties属性文件读取Properties属性文件写操作 属性文件 特殊文件概述 IO流是用来读、写文件中的数据。但是我们接触到的文件大部分都是普通的文本文件&#xff0c;普通的文本文件里面的数据是没有任何格式规范的&…

E. Greedy Shopping

线段树经典题 维护最大值和最小值 还有区间和 #include<bits/stdc.h> using namespace std; using ll long long; const int N 2e510; ll w[N]; struct Segment{ll l,r;ll val,fmin,fmax;ll lz; }tr[N<<2];int n,m;void pushup(int u){tr[u].val tr[u<<…

有关光伏电站绝缘阻抗异常排查分析-安科瑞 蒋静

近几年&#xff0c;光伏发电技术迅猛发展&#xff0c;光伏扶贫电站及分布式光伏使光伏发电走进千家万户。然而光伏发电设备运行期间仍存在隐患。及时发现并解决*常见异常运行故障&#xff0c;可以很大地提高光伏发电设备可利用率&#xff0c;是保证光伏发电设备正常运行、满足收…

class074 背包dp-分组背包、完全背包【算法】

class074 背包dp-分组背包、完全背包【算法】 算法讲解074【必备】背包dp-分组背包、完全背包 code1 P1757 通天之分组背包 // 分组背包(模版) // 给定一个正数m表示背包的容量&#xff0c;有n个货物可供挑选 // 每个货物有自己的体积(容量消耗)、价值(获得收益)、组号(分组)…

“我“的测试之路,从初级测试到测试开发,往后前景...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、测试工程师的现…

PHP基础 - 数组

在PHP中,数组是一种特殊的变量类型,可以存储多个值。PHP中有多种创建数组的方法,其中之一是使用array()函数。 1. 数值数组 带有数字 ID 键的数组 <?php $scars = array("age","name","domicile"); // 使用数组函数创建一个空数组# 人…

基于SpringBoot+Vue前后端分离的景点数据分析平台(Java毕业设计)

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…

TailwindCSS 配置可视化检查器

问题 TailwindCSS 框架为我们提供了大量默认的类和属性&#xff0c;而且开发者也能够自定义类和配置。 对于初学者来说&#xff0c;这些配置其实是比较复杂的&#xff0c;这也是tailwindcss最大的入手成本&#xff0c;开发者的记忆负担和心智负担也都比较大。 有没有办法能够…

vue3原生方法滚动列表

效果图 代码 import { ref, onBeforeUnmount, onUnmounted } from "vue"; //定时器初始化 let timer ref(null); //ref绑定初始化 let roll ref(null); //等同于vue2中的beforeDestroy onBeforeUnmount(() > {//清除定时器clearTimeout(timer.value); }); //等同…

简单实现Spring容器(二) 封装BeanDefinition对象放入Map

阶段2: // 1.编写自己的Spring容器,实现扫描包,得到bean的class对象.2.扫描将 bean 信息封装到 BeanDefinition对象,并放入到Map.思路: 1.将 bean 信息封装到 BeanDefinition对象中,再将其放入到BeanDefinitionMap集合中,集合的结构大概是 key[beanName]–value[beanDefintion…