C++中的atomic:原子

在多线程编程中,原子操作是一种重要的技术,可以确保在并发环境中进行无锁操作。C++11 引入了 std::atomic<>,提供了一种无锁的机制来操作变量,确保在并发环境中访问和修改变量时没有数据竞争。本文将介绍 std::atomic<> 的基本概念、使用场景以及实现原理,并提供一些示例代码来说明其用法。

什么是原子操作?

原子操作是指在多线程环境下,不可分割的操作,即在进行操作时不会被其他线程打断。原子操作是一种以单个事务来执行的操作,其他线程只能看到操作完成前或者完成后的资源状态,不存在中间状态可视。从底层来看,原子操作是一些硬件指令,其原子性由硬件保证,C++11 对原子操作进行了抽象,提供了统一的接口,避免使用时嵌入平台相关的代码来支持跨平台使用。

为什么需要 std::atomic<>

虽然 C++11 提供了其他高并发的 API,例如互斥锁(std::mutex)和条件变量(std::condition_variable),但这些 API 在某些情况下并不是最佳选择:

  • 性能开销:互斥锁的实现通常依赖于操作系统的系统调用,这可能带来较大的性能开销。相反,std::atomic<> 的操作通常是无锁的,直接使用底层硬件提供的原子操作,性能更高。
  • 避免死锁:使用互斥锁需要小心处理锁的获取和释放,否则容易导致死锁。std::atomic<> 不需要显式加锁和解锁,减少了编程复杂性。
  • 细粒度控制std::atomic<> 提供了对单个变量的细粒度控制,使得某些简单的并发场景可以更高效地实现。

使用 std::atomic<>

std::atomic<> 是一个模板类,可以实例化各种类型的原子操作。然而,并不是所有类型都可以实例化 std::atomic<> 模板。按照标准的说法,需要是 Trivially Copyable 的类型,简单来说就是满足以下三个条件:

  1. 连续的内存;
  2. 拷贝对象意味着按 bit 拷贝(memcpy);
  3. 没有虚函数。

用代码来表达则是自定义结构满足下面五个条件:

  • 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

示例代码

下面的代码演示了如何使用 std::atomic<> 进行各种原子操作:

#include <ostream>
#include "XLogger.h"
#include <atomic>
#include <sstream>
#include <type_traits>class AtomicInfo {};class q_atomic {
public:q_atomic() {std::atomic<int32_t> int32_atomic(100);int32_atomic++;int32_atomic--;++int32_atomic;std::atomic<bool> bool_atomic(false);/*** 读取并修改被封装的值,exchange 会将 val 指定的值替换掉之前该原子对象封装的值,并返回之前该原子对象封装的值,* 整个过程是原子的(因此exchange 操作也称为 read-modify-write 操作)。*/std::atomic<bool> atomicBool2 = bool_atomic.exchange(true, std::memory_order_relaxed);bool_atomic = true;std::atomic<AtomicInfo> info_atomic;// 这个错误信息指出 _Atomic 不能应用于 std::string 类型,因为 std::string 不是可平凡复制(trivially copyable)的类型。// C++ 中 std::atomic<> 只能用于可平凡复制的类型,例如基本数据类型(如 int、float)和某些自定义类型,但 std::string 不符合这一要求。// todo 为什么 std::string 不能直接用于 std::atomic<>?//  std::string 是一个复杂的类型,包含动态分配的内存,其复制操作涉及更多的资源管理,不能保证在并发环境下安全地进行无锁操作。//  使用锁 或 包装string std::atomic<std::shared_ptr<std::string>> atomicString(std::make_shared<std::string>("")); 规避//std::atomic<std::string> str_atomic;}void bring() {std::atomic<int32_t> atomicInt(100);/*** 修改被封装的值* void store(_Tp __d, memory_order __m = memory_order_seq_cst)* memory_order __m,指定内存序,操作的类别决定了内存次序所准许的取值。若我们没有把内存次序显式设定成上面的值,*    则默认采用最严格的内存次序,即std::memory_order_seq_cst*/atomicInt.store(100, std::memory_order_seq_cst);/*** 读取被封装的值* _Tp load(memory_order __m = memory_order_seq_cst)*/int32_t result = atomicInt.load();/*** 检查std::atomic对象是否为无锁实现。* 返回值:bool,如果当前平台支持无锁实现,返回true,否则返回false。** 判断该 std::atomic 对象是否具备 lock-free 的特性。如果某个对象满足 lock-free 特性,在多个线程访问该对象时不会导致线程阻塞* 这是一个运行时的判断(C++17提供了编译时判断constexpr is_always_lock_free()),之所以会出现无锁不确定的情况主要是因为对齐alignment。*/bool is_lock_free = atomicInt.is_lock_free();XLOG_INFO("is_lock_free: {0}", is_lock_free);//true/*** 设置原子对象的新值,并返回旧值。* 将 val 指定的值替换掉之前该原子对象封装的值,并返回之前该原子对象封装的值,整个过程是原子的(因此exchange 操作也称为 read-modify-write 操作)。*/atomicInt.exchange(12, std::memory_order_seq_cst);/*** 原子地比较并交换值。与compare_exchange_weak不同,它在失败时不会进行多次尝试。* 比较并交换被封装的值与参数 expected 所指定的值是否相等,如果:*    相等,则用 val 替换原子对象的旧值*    不相等,则用原子对象的旧值替换 expected ,因此调用该函数之后,如果被该原子对象封装的值与参数 expected 所指定的值不相等,expected 中的内容就是原子对象的旧值。** 如果值被交换,返回true,否则返回false** Tp& __e,预期值的引用* _Tp __d,要设置的新值。* memory_order __s, 成功的内存序* memory_order __f, 失败的内存序* bool compare_exchange_strong(_Tp& __e, _Tp __d, memory_order __s, memory_order __f)*/int32_t expected = 10;bool success_ = atomicInt.compare_exchange_strong(expected, 199,std::memory_order_seq_cst, std::memory_order_seq_cst);XLOG_INFO("compare_exchange_strong: {0}", success_);/*** 比较并交换(弱版本,可能会伪失败)。* 与compare_exchange_strong 不同, weak 版本的 compare-and-exchange 操作允许原子对象所封装的值与参数 expected 的物理内容相同,但却仍然返回 false,* 不过在某些需要循环操作的算法下这是可以接受的,并且在一些平台下 compare_exchange_weak 的性能更好 。如果 compare_exchange_weak 的判断确实发生了伪失败(spurious failures)——即使原子对象所封装的值与参数 expected 的物理内容相同,* 但判断操作的结果却为 false,compare_exchange_weak函数返回 false,并且参数 expected 的值不会改变。** 对于某些不需要采用循环操作的算法而言, 通常采用compare_exchange_strong 更好** 如果 atomicInt == expected,则 atomicInt = 20并返回true,否则返回false并将expected设为atomic_int的值*/bool exchanged = atomicInt.compare_exchange_weak(expected, 20);XLOG_INFO("compare_exchange_weak: {0}", exchanged);/*** 原子加法,返回旧值。可选的内存顺序,默认为memory_order_seq_cst。*/int add_ret = atomicInt.fetch_add(123);/*** 原子减法,返回旧值。可选的内存顺序,默认为memory_order_seq_cst。*/int sub_ret = atomicInt.fetch_sub(100);//原子或操作,返回旧值。atomicInt.fetch_or(1);//原子与操作,返回旧值。//old_value = 0b1100, atomic_int = 0b1000int old_value = atomicInt.fetch_and(0b1010);//原子异或操作,返回旧值。atomicInt.fetch_xor(11);//等待直到atomic_int的值不等于10atomicInt.wait(10);atomicInt.notify_one();atomicInt.notify_all();//通知等待的所有线程。/*** 实际调用了 operator T() const, 将foo 强制转换成 int 类型,然后调用 operator=().* 与 load 功能类似,也是读取被封装的值,operator T() 是类型转换(type-cast)操作,* 默认的内存序是 std::memory_order_seq_cst,如果需要指定其他的内存序,应该使用 load() 函数。*/std::atomic<int32_t> atomicInt2 = static_cast<int>(atomicInt);}void task1() {//编译时常量布尔值,用于检查类型 T 是否是可平凡复制的。一个类型是可平凡复制的,// 意味着它的复制操作(拷贝构造、拷贝赋值)都可以通过简单的内存复制(如 memcpy)完成,而不需要自定义的拷贝逻辑。bool copyable = std::is_trivially_copyable<std::string>::value;XLOG_INFO("std::string-->is_trivially_copyable: {0}", copyable);//false//编译时常量布尔值,用于检查类型 T 是否是可拷贝构造的。即,类型 T 是否可以通过拷贝构造函数创建新的对象。//用途: 在模板代码中判断类型是否支持拷贝构造,确保只有支持拷贝构造的类型才能被某些模板实例化。bool constructible = std::is_copy_constructible<std::string>::value;XLOG_INFO("std::string-->is_copy_constructible: {0}", constructible);//truebool is_copy_assignable=std::is_copy_assignable<int32_t>::value;XLOG_INFO("int32_t-->is_copy_assignable: {0}", is_copy_assignable);//true//编译时常量布尔值,用于检查类型 T 是否是可移动赋值的。即,类型 T 是否可以通过移动赋值运算符进行赋值操作。//用途: 在模板代码中判断类型是否支持移动赋值,从而优化代码性能,减少不必要的拷贝操作。bool move_assignable = std::is_move_assignable<int32_t>::value;XLOG_INFO("int32_t-->is_move_assignable: {0}", move_assignable);//truecheckTypeTraits<int32_t>();checkTypeTraits<long>();checkTypeTraits<std::string>();}template <typename T>void checkTypeTraits() {std::cout << "Is trivially copyable: " << std::is_trivially_copyable<T>::value << std::endl;std::cout << "Is copy constructible: " << std::is_copy_constructible<T>::value << std::endl;std::cout << "Is move constructible: " << std::is_move_constructible<T>::value << std::endl;std::cout << "Is copy assignable: " << std::is_copy_assignable<T>::value << std::endl;std::cout << "Is move assignable: " << std::is_move_assignable<T>::value << std::endl;}~q_atomic() {}private:
};

注意事项

  • std::atomic<> 只能用于可平凡复制的类型,例如基本数据类型(如 intfloat)和某些自定义类型,但 std::string 不符合这一要求。std::string 是一个复杂的类型,包含动态分配的内存,其复制操作涉及更多的资源管理,不能保证在并发环境下安全地进行无锁操作。
  • 可以使用 std::atomic<std::shared_ptr<std::string>> 来处理 std::string,这是一种间接方式,确保线程安全。

总结

std::atomic<> 提供了一种高效、安全的方式来处理多线程环境中的共享数据。通过利用底层硬件的原子操作指令,可以避免使用互斥锁,从而提高性能,并减少死锁的风险。在编写高并发应用程序时,合理使用 std::atomic<> 可以大大简化代码并提高程序的稳定性和性能。

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

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

相关文章

HarmonyOS应用开发者高级认证,Next版本发布后最新题库 - 单选题序号1

本来打算找到工作再整理高级的题库&#xff0c;但一直没什么面试机会。宅在家里也不知道干些什么。索性就把高级的题库整理出来了。也算有头有尾。高级的题库更新之后&#xff0c;专业性更强了&#xff0c;不是真正从事这一行的&#xff0c;很难做出来。本人就是个小菜鸡&#…

【.NET全栈】ASP.NET开发Web应用——用户控件和绘图

文章目录 前言一、用户控件1、创建用户控件2、使用用户控件3、在web.config中注册用户控件4、用户控件中公开属性5、用户控件事件6、动态加载用户控件 二、动态绘图1、基本绘图2、绘制一个自定义的图片3、在Web页面放置自定义图片4、图片格式和质量5、一个Web绘图示例程序 前言…

matlab simulink气隙局部放电仿真技术研究

1、内容简介 略 87-可以交流、咨询、答疑 2、内容说明 略 为了解决目前国内外局部放电仿真方法难以计算气隙局部放电暂态过程的问题 , 利用 MATLAB (SIMULINK ) 的公共模块库和电力系统专业模块库 , 根据单气隙局部放电仿真物理模型 , 构造了气隙局部放 电仿真计算的电…

Python运算

目录 1. 算术运算符 2. 赋值运算符 3. 比较运算符 4. 逻辑运算符 5. 位运算符 6. 成员运算符 7. 身份运算符 1. 算术运算符 运算符描述示例加法a b-减法a - b*乘法a * b/除法a / b//取整除a // b%取余数a % b**幂运算a ** b 示例&#xff1a; a 10 b 3 print(a b…

打造安全堡垒:Xcode应用权限管理全解析

打造安全堡垒&#xff1a;Xcode应用权限管理全解析 在移动应用开发中&#xff0c;权限管理是确保用户数据安全和应用正常运行的关键环节。Xcode&#xff0c;作为iOS和macOS应用开发的集成环境&#xff0c;提供了一套完善的权限管理系统。本文将深入探讨Xcode中应用权限管理的策…

树状数组优化dp

这个题目怎么去想呢&#xff0c;我们先构造前缀和&#xff0c;一般思路肯定是用两层循环&#xff0c;但是一定会超时&#xff0c;我们的数据范围是 1e5&#xff0c;那我们必须找到复杂度为 nlog n 的才行&#xff0c;所以我们可以考虑每次计算以 i 结尾的子数组的数量&#xff…

移动硬盘在苹果电脑上使用后在windows中无法读取 Win和Mac的硬盘怎么通用

在日益普及的跨平台工作环境中&#xff0c;苹果电脑与Windows PC之间的数据交换成为日常需求。然而&#xff0c;用户常面临一个困扰&#xff1a;为何苹果电脑的硬盘能在macOS下流畅运行&#xff0c;却在Windows系统中变得“水土不服”&#xff1f;这一问题核心在于硬盘格式的不…

MT19937

MT19937 文章目录 MT19937题型1 逆向extract_number[SUCTF2019]MT 题型2 预测随机数[GKCTF 2021]Random 题型3逆向twist[V&N2020 公开赛]Backtrace 题型4 逆向init扩展题型WKCTF easy_random 现成模块randcrack库Extend MT19937 Predictor库 MT19937是一种周期很长的伪随机…

安全防御:过滤技术

目录 一、URL过滤 URL过滤的方式 二、HTTP与HTTPS HTTP协议获取URL的方式 HTTP协议做控制管理的流程 HTTPS 1&#xff0c;配置SSL的解密功能 2&#xff0c;直接针对加密流量进行过滤 需求&#xff1a; 三、DNS过滤 四、内容过滤 文件过滤技术 文件过滤技术的处理流…

抖音私信卡片制作教程,使用W外链创建抖音/快手/小红书卡片

在数字营销和社交媒体日益繁荣的今天&#xff0c;利用外部链接&#xff08;W外链平台&#xff09;为抖音平台创建卡片已成为一种有效的推广手段。抖音卡片不仅可以直接将观众导向目标网页或产品&#xff0c;还能提高用户的参与度和品牌的曝光度。下面&#xff0c;我们将详细介绍…

java-selenium 截取界面验证码图片并对图片文本进行识别

参考链接 1、需要下载Tesseract工具并配置环境变量&#xff0c;步骤如下 Tesseract-OCR 下载安装和使用_tesseract-ocr下载-CSDN博客 2、需要在IDEA中导入tess4j 包&#xff1b;在pom.xml文件中输入如下内容 <!--导入Tesseract 用于识别验证码--><dependency>&l…

微信小程序开发:基础架构与配置文件

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

基于支持向量机(SVM)的数据回归预测

代码原理及流程 支持向量机&#xff08;SVM&#xff09;是一种强大的机器学习算法&#xff0c;既可以用于分类问题&#xff0c;也可以用于回归问题。在回归问题中&#xff0c;SVM 的目标是找到一个函数&#xff0c;使得预测值与实际值之间的误差最小化&#xff0c;并且保持在一…

set(集合),multiset容器及pair队组的创建

1.set的基本概念&#xff1a;所有元素再插入时自动按升序排序&#xff0c;set/multiset属于关联式容器&#xff0c;底层结构是用二叉树实现的 set与multiset区别&#xff1a; set中不允许容器中有重复的元素 multiset允许容器中有重复的元素 2.set的构造函数 3.set的大小和…

【NLP】关于参数do_sample的解释

在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;特别是在使用神经网络模型进行文本生成时&#xff0c;do_sample是一个常见的参数&#xff0c;用于控制模型生成文本的方式。具体来说&#xff0c;do_sample参数决定模型是否采用随机采样&#xff08;sampling&#x…

python—爬虫爬取视频样例

下面是一个使用Python爬虫爬取视频的基本例子。创建一个Python爬虫来爬取视频通常涉及到几个步骤&#xff1a;发送HTTP请求、解析网页内容、提取视频链接、下载视频文件。 import jsonimport requests from lxml import etreeif __name__ __main__:# UA伪装head {"User…

【js自学打卡11】生成器函数(generator函数)的使用总结+代码举例

力扣的js入门免费题刷完了&#xff0c;开始自己找题练练&#xff0c;顺便捡捡知识点 力扣2649 1.思路 一眼递归&#xff0c;但事实证明也可以直接flat手撕。 arr.flat(Infinity) //直接扁平化到最底层涉及到了一些关于生成器和异步编程相关的知识点&#xff0c;学一下。 2.…

web前端 React 框架面试200题(三)

面试题 65. 在使用 React Router时&#xff0c;如何获取当前页面的路由或浏览器中地址栏中的地址&#xff1f; 参考回答&#xff1a; 在当前组件的 props中&#xff0c;包含 location属性对象&#xff0c;包含当前页面路由地址信息&#xff0c;在 match中存储当前路由的参数等…

数字图像处理笔记(二)---- 像素加图像统计特征

系列文章目录 文章目录 系列文章目录前言一、认识数字图像二、图像的数学描述二、图像的统计特征总结 前言 慕课视频地址 一、认识数字图像 图像分为模拟图像和数字图像。要想获得数字图像需要通过采样量化编码等过程。 量化和采样的过程是将模拟信号转化为数字信号。编码的过…