【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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

tomcat启动 页面报错404(3种解决方案)

1.端口号被占用 修改配置文件下的端口号&#xff1a;\apache-tomcat-8.5.57\conf\server.xml 我这里修改端口号为&#xff1a;9999 修改后页面输入&#xff1a;http://localhost:9999/ 2.没有配置环境变量 配置环境变量请查看&#xff1a;保姆级教程 3.查看下 apache-tomca…

SpringBoot Seata 死锁问题排查

现象描述&#xff1a;Spring Boot项目&#xff0c;启动的时候卡住了&#xff0c;一直卡在那里不动&#xff0c;没有报错&#xff0c;也没有日志输出 但是&#xff0c;奇怪的是&#xff0c;本地可以正常启动 好吧&#xff0c;姑且先不深究为什么本地可以启动而部署到服务器上就无…

【Flink系列五】Checkpoint及Barrier原理

本章内容 一致性检查点从检查点恢复状态检查点实现算法-barrier保存点Savepoint状态后端&#xff08;state backend&#xff09; 本文先设置一个前提&#xff0c;流处理的数据都是可回放的&#xff08;可以理解成消费的kafka的数据&#xff09; 一致性检查点&#xff08;che…

单片机第三季-第四课:STM32下载、MDK和调试器

目录 1&#xff0c;扩展板使用的STM32芯片类型 2&#xff0c;使用普中科技软件下载程序 3&#xff0c;keil介绍 4&#xff0c;JLINK调试器介绍 5&#xff0c;使用普中的调试器进行debug 6&#xff0c;使用Simulator仿真 1&#xff0c;扩展板使用的STM32芯片类型 扩展版…

如何用docker在自己服务器上部署springboot项目

一、将springboot项目打包 1、maven clean项目 2、maven package项目 打包成功之后生成jar文件&#xff08;在target目录下&#xff09; 3、为Java创建Dockerfile 引入jdk8的Docker镜像 FROM openjdk:8 为了使运行其余命令时更容易&#xff0c;让我们设置映像的工作目录。这将…

1、初识 llvm源码编译 及virtualbox和ubuntu环境搭建

很久没更新了&#xff0c;最近准备研究逆向和加固&#xff0c;于是跟着看雪hanbing老师学习彻底搞懂ollvm&#xff0c;终于把所有流程跑通了&#xff0c;中间遇到了太多的坑&#xff0c;所以必须记录一下&#xff0c;能避免自己和帮助他人最好。 环境搭建太重要了&#xff0c;…

26、pytest使用allure解读

官方实例 # content of pytest_quick_start_test.py import allurepytestmark [allure.epic("My first epic"), allure.feature("Quick start feature")]allure.id(1) allure.story("Simple story") allure.title("test_allure_simple_te…

LabVIEW使用单板RIO开发远程监控电源信号

LabVIEW使用单板RIO开发远程监控电源信号 设计和构建用于智能电网的本地功耗分析系统&#xff0c;主要服务于领先的电力监控设备设计者和制造商。随着智能电网投资的增加&#xff0c;对于能够有效处理替代电源&#xff08;如太阳能和风能&#xff09;间歇性功率水平的技术需求…

vue 提交表单重复点击,重复提交防抖问题

问题&#xff1a;用户点击保存时&#xff0c;可能会多次点击。导致生成重复数据。 目标&#xff1a;多次点击时&#xff0c;1s内只允许提交一次数据。 解决方案&#xff1a; 1.在utils文件夹创建文件preventReClick.js export default {install (Vue) {// 防止按钮重复点击V…

lv11 嵌入式开发 IIC(上) 19

目录 1 IIC总线简介 1.1 串行、半双工&#xff08;同时只能1收或者1发&#xff09; 1.2 IIC总线通信过程 2 IIC总线信号实现 2.1 IIC总线寻址方式 2.2 起始信号和停止信号 2.3 字节传送与应答 2.4 同步信号 2.5 典型IIC时序 3 练习 1 IIC总线简介 1.1 串行、半双工&a…

2.5D封装与3D IC封装主流产品介绍

2.5D封装和3D IC封装都是新兴的半导体封装技术&#xff0c;它们都可以实现芯片间的高速、高密度互连&#xff0c;从而提高系统的性能和集成度。但是它们之间也存在一些差异和异同点。 1、3D 结构与 2.5D 有何不同&#xff1f; 首先&#xff0c;2.5D封装和3D IC封装的互连方式有…

Chrome浏览器禁止更新策略

在做爬虫过程中&#xff0c;需要用到Selenium驱动浏览器去做动态爬虫 这里我一般用到的是Chrome谷歌浏览器进行爬虫 但是&#xff0c;目前python和Chrome浏览器适配最好的是110.版本 尽管我用了很多种方法 去取消浏览器自动更新 但是 过一段时间 浏览器总是会自动更新到最新…

Linux Docker 安装Nginx

1.21、查看可用的Nginx版本 访问Nginx镜像库地址&#xff1a;https://hub.docker.com/_/nginx 2、拉取指定版本的Nginx镜像 docker pull nginx:latest #安装最新版 docker pull nginx:1.25.3 #安装指定版本的Nginx 3、查看本地镜像 docker images 4、根据镜像创建并运行…

TCP单聊和UDP群聊

TCP协议单聊 服务端&#xff1a; import java.awt.BorderLayout; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.V…

geemap学习笔记022:如何找出一年中最绿的一天?

前言 这虽然是一个问题&#xff0c;但是解决这个问题之后&#xff0c;就会学习到很多的内容。包括如何计算NDVI、如何进行镶嵌、如何获取影像的时间等等。 1 导入库并显示地图 import ee import geemapee.Initialize() Map geemap.Map() Map2 定义一个感兴趣区&#xff08;…

新书推荐——《Copilot和ChatGPT编程体验:挑战24个正则表达式难题》

《Copilot和ChatGPT编程体验&#xff1a;挑战24个正则表达式难题》呈现了两方竞争的格局。一方是专业程序员David Q. Mertz&#xff0c;是网络上最受欢迎的正则表达式教程的作者。另一方则是强大的AI编程工具OpenAI ChatGPT和GitHub Copilot。 比赛规则如下&#xff1a;David编…

【Flink系列二】如何计算Job并行度及slots数量

接上文的问题 并行的任务&#xff0c;需要占用多少slot &#xff1f;一个流处理程序&#xff0c;需要包含多少个任务 首先明确一下概念 slot&#xff1a;TM上分配资源的最小单元&#xff0c;它代表的是资源&#xff08;比如1G内存&#xff0c;而非线程的概念&#xff0c;好多…

PCL 点云最小二乘法拟合二维圆

文章目录 一、原理概述二、实现代码三、实现效果参考资料一、原理概述 二、实现代码 // 标准文件 #include <iostream>// PCL #include <pcl/io/pcd_io.h>

Python中的并发编程(2)线程的实现

Python中线程的实现 1. 线程 在Python中&#xff0c;threading 库提供了线程的接口。我们通过threading 中提供的接口创建、启动、同步线程。 例1. 使用线程旋转指针 想象一个场景&#xff1a;程序执行了一个耗时较长的操作&#xff0c;如复制一个大文件&#xff0c;我们希…

如何加快网络攻击发现速度

网络攻击可能会摧毁受害者。例如&#xff0c;米高梅度假村 (MGM Resorts) 预计将因 9 月份的网络攻击而遭受 1 亿美元的损失。 鲜为人知的是&#xff0c;在许多情况下&#xff0c;借助网络攻击发现可以预防网络攻击或将其消灭在萌芽状态。 威胁行为者变得越来越复杂&#xff…