C++11标准库 原子变量 atomic 梳理

news/2024/11/15 9:56:23/文章来源:https://www.cnblogs.com/DSCL-ing/p/18302143

目录
  • <atomic>
    • 原子操作的概念
    • CAS实现原理
    • CAS操作的伪代码:
      • 使用CAS完成变量的原子操作:
      • CAS 操作的保证
      • lock和锁的概念
    • atomic模板类
      • 构造函数
      • 公共成员函数:
      • atomic与互斥锁的效率比对

<atomic>

C++11提供了一个原子类型std::atomic,通过这个原子类型管理的内部变量就可以称之为原子变量.

原子类型只支持bool、char、int、long、指针等整型类型作为模板参数(不支持浮点类型和复合类型)。

原子操作的概念

在原子操作过程中,线程不会发生调度。原子操作是一种不可中断的操作,一旦开始执行,就会一直运行到结束,不会被其他线程打断。因此,在原子操作期间,操作系统的线程调度器不会将CPU分配给其他线程,以确保原子操作的完整性和正确性。

CAS实现原理

CAS(Compare-And-Swap 或 Compare-And-Set)是一种原子操作,用于实现多线程编程中的同步机制。它的基本原理是通过比较和交换来确保一个变量在多个线程访问时的正确性。CAS是许多并发数据结构和算法的基础,比如无锁队列、无锁栈等。

CAS操作的伪代码:

硬件提供的原子性支持,如汇编lock指令等 //整个代码执行都是不可中断的(不是串行)
(asm::lock:)
bool compare_and_swap(int* addr, int expected, int new_value) {int current = *addr; // 读取当前值if (current == expected) { //比较当前值和预期值*addr = new_value; // 如果当前值等于预期值,则更新return true;}return false; // 否则,不更新
}
(asm::unlock:)

使用CAS完成变量的原子操作:

// 共享的整数变量
int shared_var = 10; //ABA问题:无法得知shared_var的全程状态.被修改去又修改回,起止状态不变,过程改变,状态在过程中发生改变CAS却误以为没有改变过.可能导致发生一些隐晦的错误.
//避免ABA,有做变量版本号机制(tag,flag等),或更复杂的实现. -- 有的说回退机制可以:不可以,单单回退机制无法解决ABA问题void thread_function() {int expected_value = 10; // 希望 shared_var 的当前值是一般是shared_var的原始值,即10int new_value = 20;      // 目的 将shared_var 更新为 20// 使用 CAS 操作来原子地更新 shared_varbool success = compare_and_swap(&shared_var, expected_value, new_value);if (success) {// CAS 操作成功// 这里可以继续处理其他逻辑} else {// CAS 操作失败,shared_var 的值不是我们预期的 expected_value// 可能需要重试或处理其他情况}
}

CAS 操作的保证

尽管可能会发生 CPU 上下文切换,但 CAS 操作的保证在于其执行过程中的不可中断性。即使发生了 CPU 上下文切换,操作系统和处理器会保证 CAS 操作的执行过程是原子的,其他线程或处理器无法在 CAS 操作期间对其操作的内存位置进行修改。

(CAS == 逻辑检查变量状态+硬件支持语句原子性)

lock和锁的概念

汇编中的lock指令前缀和编程中的锁(如互斥锁)虽然在概念上都涉及到同步和确保操作的原子性,但它们是不同的东西,作用机制和应用场景也不同。

  • lock指令前缀用于多处理器系统中的汇编指令,确保特定的内存操作在多个处理器上是原子的。它的作用是锁住总线或使用缓存一致性协议,确保在指令执行期间其他处理器无法访问涉及的内存位置。lock前缀常用于需要原子操作的低级同步机制中,例如在实现原子性增减、比较交换等操作时。

  • 编程中的锁(如互斥锁、读写锁)是一种高级同步原语,用于确保同一时刻只有一个线程可以访问临界区(共享资源)。用于保护临界区,防止数据竞争,确保线程安全。常见的应用场景包括多线程程序中的共享数据访问、数据库中的事务管理等。

atomic模板类

构造函数

atomic() noexcept = default;constexpr atomic( T desired ) noexcept;atomic( const atomic& ) = delete;      //禁止拷贝

desired:用以初始化的值

空对象初始化方式:

MSVC中类成员atomic类型允许带有缺省值(MSVC优化).但这不是标准C++行为.atomic类型的成员只能在构造函数中完成初始化.

而GCC中不允许使用缺省值,是由编译器实现的.

如果我们要自己实现一个不允许使用缺省值的类型,则可以显式定义构造函数+explicitimage-20240706220220790

公共成员函数:

  • operator=
//模拟原生变量的行为,赋值给原生变量
T operator=( T desired ) noexcept;
T operator=( T desired ) volatile noexcept;//禁止拷贝赋值重载
atomic& operator=( const atomic& ) = delete;
atomic& operator=( const atomic& ) volatile = delete;
  • store

和operator=功能一样,将数据存储到原生变量中,没有返回值

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;

desired:存储到原子变量中的值
order :程序代码内存执行顺序约束(跨平台使用)

volatile:保证内存可见性,修饰函数时表示可以通过该函数访问到volatile修饰的变量.

  • load

取出原生变量的值.

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;
  • operator T()
operator T() const volatile noexcept;
operator T() const noexcept;

类类型转换运算符重载.将原子变量类型转化成T类型.

意思是,通过原子变量得到的值就是T类型的值,即得到原生变量的值.等同于load().

示例:

image-20240706221939479

  • exchange
T exchange (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
T exchange (T val, memory_order sync = memory_order_seq_cst) noexcept;

和store()功能类似,将数据存储/覆盖到原生变量中.

不同的功能是,exchange还会返回被覆盖的旧数据.

  • compare_exchange_weak & compare_exchange_strong

使用C++11原子量实现自旋锁 - 兔晓侠 - 博客园 (cnblogs.com)

compare_exchange_weak 与 compare_exchange_strong 主要的区别在于内存中的值与expected相等的时候,CAS操作是否一定能成功.

compare_exchange_weak有概率会返回失败,而compare_exchange_strong则一定会成功

因此,compare_exchange_weak必须与循环搭配使用来保证在失败的时候重试CAS操作。得到的好处是在某些平台上compare_exchange_weak性能更好。按照上面的模型,我们本来就要和while搭配使用,可以使用compare_exchange_weak。

最后内存序的选择没有特殊需求直接使用默认的std::memory_order_seq_cst。

  • atomic提供的一组用于对原子变量进行修改的特化函数

atomic - C++ Reference (cplusplus.com)

image-20240706222538244

分别是 加、减、按位与、按位或、按位异或、自增、自减、赋值类 操作。

各个 operator 对应的 fetch_ 操作表

操作符 操作符重载函数 等级的成员函数 整形 指针 其他
+ atomic::operator+= atomic::fetch_add
- atomic::operator-= atomic::fetch_sub
& atomic::operator&= atomic::fetch_and
| atomic::operator|= atomic::fetch_or
^ atomic::operator^= atomic::fetch_xor

image-20240706222939115

  • C++11还为常用的atomic提供了别名

std::atomic - cppreference.com

有很多,举例出一部分

别名 原始类型定义
atomic_bool(C++11) std::atomic
atomic_char(C++11) std::atomic
atomic_schar(C++11) std::atomic
atomic_uchar(C++11) std::atomic
atomic_short(C++11) std::atomic
atomic_ushort(C++11) std::atomic
atomic_int(C++11) std::atomic
atomic_uint(C++11) std::atomic
atomic_long(C++11) std::atomic
atomic_ulong(C++11) std::atomic
atomic_llong(C++11) std::atomic
atomic_ullong(C++11) std::atomic

atomic与互斥锁的效率比对

例程:

#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <functional>
using namespace std;struct Counter
{Counter():m_value(0){}void increment(){for (int i = 0; i < 100000000; ++i){lock_guard<mutex> locker(m_mutex);m_value++;}}void decrement(){for (int i = 0; i < 100000000; ++i){lock_guard<mutex> locker(m_mutex);m_value--;}}int m_value = 0;//std::atomic<int> m_value ;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();std::cout<<"Counter: "<<c.m_value<<"\n";return 0;
}

GCC下互斥锁版本耗时:

image-20240706213919972

GCC下原子变量版本耗时:

image-20240706214047274

显然,在基本类型线程同步的场景下,原子变量性能更为高效.

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

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

相关文章

使用ML.NET训练一个属于自己的图像分类模型,对图像进行分类就这么简单!

前言 今天大姚给大家分享一个.NET开源、免费、跨平台(支持Windows、Linux、macOS多个操作系统)的机器学习框架:ML.NET。并且本文将会带你快速使用ML.NET训练一个属于自己的图像分类模型,对图像进行分类。ML.NET框架介绍 ML.NET 允许开发人员在其 .NET 应用程序中轻松构建、…

网页交互

单击 选择元素 (html)<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title&g…

读人工智能全传13人工智能导致的问题2

读人工智能全传13人工智能导致的问题21. 机器人sha手 1.1. 自主57的话题总是带有强烈的煽动性,许多人会本能地厌恶它,认为这样的系统是不道德的,永远不该被建立 1.2. 自主57的讨论大多源于战争中使用得越来越频繁的无人机 1.3. 无人机 1.3.1. 人驾驶的飞机,在菌用领域,它可…

万字:清结算体系,全局方案深度解析

本文分享了头部支付机构是如何做清结算的,在做和带领大家打通支付的底层处理原理,内核中的内核,分享给大家。支付机构帮助交易平台代收代付交易款,那么就需要先从消费者发卡行把钱拿过来,然后再结算给交易平台;对于交易平台也是一样的道理,要帮店家卖东西,需要帮忙通过…

【THM】tomghost练习

先努力成为脚本小子【THM】tomghost练习 与本文相关的TryHackMe实验房间链接:TryHackMe | Room details 简介:识别最近的漏洞,以尝试利用系统或读取你没有权限访问的文件。 **你能完成这个挑战吗? **机器可能需要长达5分钟的启动和配置。 管理员记录:这个房间的用户名包含不…

电动自行车 LED 大灯不亮故障分析和维修教程 All In One

电动自行车 LED 大灯不亮故障分析和维修教程 All In One 电动自行车内置的 LED 前大灯,在骑行途中突然不亮了 ❌电动自行车 LED 大灯不亮故障分析和维修教程 All In One自己动手,丰衣足食问题表象 电动自行车内置的 LED 前大灯,在骑行途中突然不亮了 ❌故障排查转向灯正常 行…

边缘计算是什么?为什么边缘计算平台如此重要?

边缘计算是一种在物联网设备、传感器、嵌入式设备等边缘节点上进行数据处理、分析和存储的计算模式。边缘计算利用设备本身的计算能力,将处理和存储任务从中央云端转移到边缘设备上。这样可以减少数据传输的延迟和网络拥塞,并提高数据隐私和安全性。而边缘计算平台之所以如此…

《《《maven仓库下载jar包

地址:Maven Repository: Search/Browse/Explore (mvnrepository.com) 1.搜索需要的jar包,下面以 easyExcel 为例 2.每天多努力一点,你将会变得更好。

Go语言基于go module方式管理包(package)

目录一.Go Modules发展史1 前言2 早期第三方包存储在GOPATH路径3 vendor阶段4 社区管理工具层出不穷5 go modules官宣官方管理工具二.go module介绍1 GO111MODULE环境变量2 GOPROXY3 GOSUMDB4 GONOPROXY/GONOSUMDB/GOPRIVATE5 go.mod文件6 go.sum文件7 依赖保存位置三. 3.go …

基于go module方式管理包(package)

目录一.Go Modules发展史1 前言2 早期第三方包存储在GOPATH路径3 vendor阶段4 社区管理工具层出不穷5 go modules官宣官方管理工具二.go module介绍1 GO111MODULE环境变量2 GOPROXY3 GOSUMDB4 GONOPROXY/GONOSUMDB/GOPRIVATE5 go.mod文件6 go.sum文件7 依赖保存位置三. 3.go …

rsync+inotify数据的实时同步

一、实时同步技术介绍 1.工作原理:要利用监控服务(inotify),监控同步数据服务器目录中信息的变化发现目录中数据产生变化,就利用rsync服务推送到备份服务器上2.inotify 异步的文件系统事件监控机制,利用事件驱动机制,而无须通过诸如cron等的轮询机制来获取事件,linux内…

Mysql在数据插入后立即获取插入的Id

项目中有需要再数据插入后实用插入的Id,这里使用的是useGeneratedKeys什么是useGeneratedKeys? 官方的说法是该参数的作用是:“允许JDBC支持自动生成主键,需要驱动兼容”,如何理解这句话的意思?其本意是说:对于支持自动生成记录主键的数据库,如:MySQL,SQL Server,此…