C++ 原子变量

C++ 原子变量

文章目录

  • C++ 原子变量
    • 1. 原子变量是什么?
    • 2. 原子操作的特点
    • 3. 原子变量的作用
      • 1. 多线程安全的共享数据访问
      • 2. 替代锁机制
      • 3. 实现低级同步算法
    • 4. 原子变量的常见操作
    • 5. 内存顺序(Memory Ordering)
      • 内存顺序控制在原子变量中的作用
      • 如何影响程序行为
    • 6. 示例:比较并交换(CAS)
    • 7. 总结

在 C++11 中,原子变量(atomic variables)是指通过 std::atomic 类型封装的变量,它们的操作在多线程环境中是 原子的,即不可分割的。这意味着对原子变量的操作(如读取、写入、更新等)是线程安全的,不会被其他线程的操作干扰或中断。

1. 原子变量是什么?

原子变量是 C++11 引入的,用于在多线程程序中确保对共享数据的访问是安全的。使用 std::atomic 类型,可以对变量进行原子操作,从而避免了传统的锁机制(如互斥锁 std::mutex)的使用。

std::atomic 是一个模板类,支持多种数据类型(如 int, bool, pointer 等)。它保证对该变量的操作是原子的,即所有操作要么完全成功,要么完全失败,不会被中断、重排或与其他线程的操作发生冲突。

2. 原子操作的特点

原子操作有几个显著的特点:

  • 不可分割:原子操作要么完全执行,要么完全不执行,不会被其他线程的操作打断。
  • 线程安全:由于原子性,多个线程可以同时访问同一个原子变量,而无需显式地加锁(如 std::mutex)。这对于提升并发性能非常重要。
  • 避免数据竞争:通过确保每个操作是原子性的,避免了数据竞争(data race)的问题。

3. 原子变量的作用

原子变量在并发编程中起着非常重要的作用,它们的主要用途包括:

1. 多线程安全的共享数据访问

在多线程程序中,多个线程可能会同时访问并修改同一共享数据。使用常规的变量时,可能会发生数据竞争(data race),导致数据不一致或者程序崩溃。而原子变量可以确保对其的操作是原子的,从而避免了数据竞争。

例如,下面的代码演示了如何使用原子变量来安全地进行递增操作:

#include <iostream>
#include <atomic>
#include <thread>std::atomic<int> counter(0);  // 定义一个原子变量void increment() {for (int i = 0; i < 10000; ++i) {counter.fetch_add(1, std::memory_order_relaxed);  // 原子加 1}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final counter value: " << counter.load() << std::endl;  // 安全读取原子变量return 0;
}

在上述代码中,两个线程 t1t2 都在对 counter 进行自增操作。由于 counter 是原子变量,这样的操作是线程安全的,避免了数据竞争。

2. 替代锁机制

原子操作通过硬件提供的原子指令,能够高效地完成一些常见的同步任务,例如递增、递减、交换、比较和交换等操作,而不需要使用传统的锁机制(如 std::mutex)。这可以有效减少锁的使用,提高程序的并发性能。

传统的锁机制(如互斥锁)通常会引入线程上下文切换和性能开销,而原子变量的操作是直接通过硬件实现的,通常具有更高的效率。

3. 实现低级同步算法

在一些低级的并发数据结构和算法中,原子变量常常用于实现高效的同步机制,例如无锁队列、栈、哈希表等。通过原子操作,可以避免锁的使用,从而提高并发度和程序的整体性能。

4. 原子变量的常见操作

C++11 中的 std::atomic 提供了多种原子操作,这些操作可以保证对原子变量的修改是线程安全的。常见的操作包括:

  • load(): 读取原子变量的值。
  • store(): 设置原子变量的值。
  • exchange(): 将原子变量的值替换为指定值,并返回原先的值。
  • compare_exchange_weak() / compare_exchange_strong(): 比较并交换(CAS,Compare and Swap)操作,只有当当前值等于预期值时才会交换。
  • fetch_add() / fetch_sub(): 原子地执行加法或减法。
  • fetch_and() / fetch_or() / fetch_xor(): 原子地执行按位与、按位或、按位异或。

例如:

std::atomic<int> value(0);// 原子加法
value.fetch_add(1, std::memory_order_relaxed);// 比较并交换
int expected = 0;
value.compare_exchange_weak(expected, 1);// 读取原子变量
int current_value = value.load();

5. 内存顺序(Memory Ordering)

std::atomic 操作有一个重要的概念——内存顺序。内存顺序控制了操作在多线程程序中的可见性和执行顺序。内存顺序控制决定了在多线程程序中,不同线程间对共享内存的操作顺序。因为现代处理器在执行程序时,可能出于性能考虑进行指令重排和缓存优化,这可能导致不同线程间看到的内存访问顺序不同。

例如,一个线程修改了某个变量的值,另一个线程读取该变量时,可能会看到不同的值,这就是由于内存重排或缓存的原因。为了解决这个问题,可以通过内存顺序控制来指定操作的顺序和可见性,以确保线程之间的同步和数据一致性。

C++11 提供了几种内存顺序选项:

  • memory_order_relaxed: 仅保证原子性,不强制同步顺序。
  • memory_order_consume: 保证当前操作依赖的所有操作在其前面执行。
  • memory_order_acquire: 保证当前操作之前的所有操作不会被重排。
  • memory_order_release: 保证当前操作之后的所有操作不会被重排。
  • memory_order_acq_rel: 同时具有 acquirerelease 的效果。
  • memory_order_seq_cst: 默认的内存顺序,保证所有操作的严格顺序一致。

内存顺序控制在原子变量中的作用

在使用原子变量时,操作不仅仅涉及到对数据的修改,还需要控制操作的可见性(不同线程是否看到相同的值)和顺序性(操作的执行顺序)。

C++11、Java等语言的原子操作库提供了对内存顺序的明确控制,通常有以下几种内存顺序:

  1. 顺序一致性(Sequentially Consistent, memory_order_seq_cst
    • 默认内存顺序。保证所有线程对原子变量的所有操作按照严格的顺序进行,所有线程都能看到一致的执行顺序。这种顺序是最强的同步保证,但也可能牺牲性能。
  2. 获取-释放(Acquire-Release, memory_order_acquirememory_order_release
    • 获取(Acquire):用于确保当前线程在执行某个操作之前,所有前面的操作完成。这通常用于读取原子变量时,确保读取到最新的数据。
    • 释放(Release):用于确保当前线程在执行某个操作之后,所有之后的操作完成。这通常用于写入原子变量时,确保所有更新已被其他线程可见。
    • 获取-释放组合(memory_order_acq_rel):同时包含获取和释放的功能。它常见于涉及原子变量的加减操作,确保操作前后的顺序。
  3. 无序(Unordered, memory_order_relaxed
    • 不对操作的顺序进行约束。线程对原子变量的操作不需要遵循任何内存顺序的规则,这样可以提高性能,但可能导致不同线程看到不一致的内存状态。
  4. 明确顺序(Ordered, memory_order_consumememory_order_acquire
    • memory_order_consume 比较少用,通常被memory_order_acquire替代。其作用是确保之前的数据读取操作先于后续的操作。

如何影响程序行为

通过内存顺序控制,程序员可以精细控制线程间的同步,避免线程间的数据竞争,同时也能够优化程序性能。

  • 内存顺序较弱(如memory_order_relaxed):适用于某些不需要严格同步的场景,可以提高性能,因为它不强制执行指令顺序。
  • 内存顺序较强(如memory_order_seq_cst):适用于需要高度一致性的场景,如多个线程修改共享数据时,需要严格的顺序保证。
#include <atomic>
#include <iostream>
#include <thread>std::atomic<int> data = 0;void producer() {data.store(42, std::memory_order_release);  // Release write
}void consumer() {while (data.load(std::memory_order_acquire) != 42) {  // Acquire readstd::this_thread::sleep_for(std::chrono::milliseconds(10));}std::cout << "Data is: " << data.load() << std::endl;
}int main() {std::thread t1(producer);std::thread t2(consumer);t1.join();t2.join();return 0;
}

在这个示例中,producer线程将数据写入原子变量并使用memory_order_release,而consumer线程在读取时使用memory_order_acquire,这样可以确保consumer线程在读取数据时,所有写入data的操作都已经完成。原子变量的操作通过内存顺序控制来保证多线程程序中的正确性与性能。不同的内存顺序(如acquirereleaseseq_cst等)允许程序员控制操作的同步方式,从而满足不同的并发需求。

6. 示例:比较并交换(CAS)

#include <iostream>
#include <atomic>std::atomic<int> counter(0);bool compare_exchange_example() {int expected = 0;return counter.compare_exchange_weak(expected, 1); // 如果当前值是 0,则将其更改为 1
}int main() {bool success = compare_exchange_example();std::cout << "Exchange success: " << success << ", new counter value: " << counter.load() << std::endl;return 0;
}

7. 总结

原子变量 (std::atomic) 在 C++11 中的引入,主要用于支持多线程程序中的共享数据的安全操作。通过 std::atomic 类型,可以避免使用传统的锁机制来保证线程安全,从而提高程序的并发性和性能。它们的主要用途包括确保多线程环境中共享数据的正确访问,替代锁机制,和在一些低级同步算法中的应用。

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

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

相关文章

前后端分离架构设计与实现:构建现代Web应用的基石

前后端分离架构设计与实现&#xff1a;构建现代Web应用的基石 引言 随着互联网技术的发展&#xff0c;Web应用变得越来越复杂和多样化。传统的单体式架构难以满足快速迭代、团队协作以及性能优化的需求。前后端分离架构应运而生&#xff0c;它不仅提高了开发效率&#xff0c;…

了解RabbitMQ的工作原理

RabbitMQ是一个开源的消息代理系统&#xff0c;实现了高级消息队列协议&#xff08;AMQP&#xff09;。在现代分布式系统中&#xff0c;特别是在微服务架构中&#xff0c;RabbitMQ有广泛的应用。本文将详细介绍RabbitMQ的工作原理&#xff0c;并通过实践案例帮助读者理解和应用…

Excel中公式和函数的区别

Excel中公式和函数的区别 概念讲解例子公式函数 详细介绍函数面板最后再次进行演示操作文档 概念讲解 公式是用户自己编写的表达式&#xff0c;而函数是由Excel预定义的操作。公式可以包含各种数学运算符和逻辑表达式&#xff0c;函数则是执行特定任务的工具。公式可以引用其他…

分布式搜索引擎之elasticsearch基本使用3

分布式搜索引擎之elasticsearch基本使用3 1.部署单点es 1.1.创建网络 因为我们还需要部署kibana容器&#xff0c;因此需要让es和kibana容器互联。这里先创建一个网络&#xff1a; docker network create es-net1.2.加载镜像 这里我们采用elasticsearch的7.12.1版本的镜像&…

【FlutterDart】 拖动改变 widget 的窗口尺寸大小GestureDetector~简单实现(10 /100)

上效果 预期的是通过拖动一条边界线改变窗口大小&#xff0c;类似vscode里拖动效果。这个是简单的拖动实现 上代码&#xff1a; import package:flutter/material.dart;class MyDraggableViewDemo extends StatelessWidget {const MyDraggableViewDemo({super.key});override…

使用Dinky快速提交Flink operator任务

官网地址&#xff1a;K8s集成 | Dinky 1.目前使用版本 Dinky1.2.0、Flink1.18.1、Flink operator0.10.0 2.制作镜像 2.1创建DockerFile ARG FLINK_VERSION1.18.1 FROM flink:${FLINK_VERSION}-scala_2.12 RUN mkdir -p /opt/flink/usrlib COPY commons-cli-1.3.1.jar …

查找路由器的管理后台ip【通用找IP】

需求&#xff1a; 刚刚搞了个【小米】路由器&#xff0c;我想进路由的管理后台&#xff0c;提示&#xff1a;安装xx的路由管家&#xff0c;我不想安装 但是无法找到这个管理后台。 而且我是用这个路由作为中继&#xff0c;那么这个路由的ip就会经常更换 尝试通过网上搜索引擎来…

【大数据】(选修)实验4 安装熟悉HBase数据库并实践

实验4 安装熟悉HBase数据库并实践 1、实验目的 (1)理解HBase在Hadoop体系结构中的角色; (2)熟练使用HBase操作常用的Shell命令; (3)熟悉HBase操作常用的Java API。 2、实验平台 操作系统:Linux Hadoop版本:2.6.0或以上版本 HBase版本:1.1.2或以上版本 JDK版…

Flutter鸿蒙化 在鸿蒙应用中添加Flutter页面

前言 今天这节课我们讲一下 在鸿蒙应用中添加Flutter页面。 作用: 之前有很多朋友和网友问我鸿蒙能不能使用Flutter开发,他们的项目已经用Flutter开发成熟了有什么好的方案呢,今天讲到这个就可以很好的解决他们的问题,例如我们正式项目中可能是一部分native 开发 一部分…

vue3-watchEffect异步依赖收集

当 b 更新时 a 并不会更新&#xff0c;因为watchEffect的依赖收集在该案例中停止于await asyncFn()&#xff0c;也就是只会收集同步代码的依赖&#xff0c;await 之后的异步代码的依赖并不会收集到 <template> <div>a: {{ a }} <br>b: {{ b }} <br>&l…

【通识安全】煤气中毒急救的处置

1.煤气中毒的主要症状与体征一氧化碳中毒&#xff0c;其中毒症状一般分为轻、中、重三种。 (1)轻度&#xff1a;仅有头晕、头痛、眼花、心慌、胸闷、恶心等症状。如迅速打开门窗&#xff0c;或将病人移出中毒环境&#xff0c;使之吸入新鲜空气和休息&#xff0c;给些热饮料&am…

【UI自动化测试】selenium八种定位方式

&#x1f3e1;个人主页&#xff1a;謬熙&#xff0c;欢迎各位大佬到访❤️❤️❤️~ &#x1f472;个人简介&#xff1a;本人编程小白&#xff0c;正在学习互联网求职知识…… 如果您觉得本文对您有帮助的话&#xff0c;记得点赞&#x1f44d;、收藏⭐️、评论&#x1f4ac;&am…

redis各种数据类型介绍

Redis 是一种高性能的键值存储数据库&#xff0c;它支持多种数据类型&#xff0c;使得开发者可以灵活地存储和操作数据。以下是 Redis 支持的主要数据类型及其介绍&#xff1a; 1. 字符串&#xff08;String&#xff09; 字符串是 Redis 中最基本的数据类型&#xff0c;它可以存…

【Linux】Linux命令

目录 ​编辑 系统维护命令 man man&#xff1a;查看 man 手册 sudo passwd 用户名&#xff1a;修改用户密码 su&#xff1a;切换用户 echo ”输出内容“&#xff1a;向终端输出内容&#xff0c;默认换行 date查看当前系统的日期 clear&#xff1a;清屏 df -Th /df -h&…

关机重启后,GitLab服务异常

整理机房,关闭了所有主机重新上架。 上架后开机,所有主机硬件启动正常。 其中一台GitLab服务器启动正常,使用gitlab-ctl status查看服务业正常。 但使用web登陆却失败,如下图: 反复测试,发现无论使用正确密码还是错误密码都是同样的提示。很大可能是数据库的问题。 使…

【嵌入式硬件】直流电机驱动相关

项目场景&#xff1a; 驱动履带车&#xff08;双直流电机&#xff09;前进、后退、转弯 问题描述 电机驱动MOS管烧毁 电机驱动采用IR2104STRH1R403NL的H桥方案&#xff08;这是修改之后的图&#xff09; 原因分析&#xff1a; 1.主要原因是4路PWM没有限幅&#xff0c;修改…

Python编程实例-特征向量与特征值编程实现

特征向量与特征值编程实现 文章目录 特征向量与特征值编程实现1、什么是特征向量2、特征向量背后的直觉3、为什么特征向量很重要?4、如何计算特征向量?4、特征向量Python实现5、可视化特征向量6、总结线性代数是许多高级数学概念的基石,广泛应用于数据科学、机器学习、计算机…

OpenCV在现代社会中的应用

OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源计算机视觉库&#xff0c;广泛应用于图像处理、计算机视觉以及机器学习领域。随着科技的发展&#xff0c;OpenCV在现代社会中扮演着越来越重要的角色&#xff0c;尤其在以下五个方向中取得了显著的…

开源模型应用落地-qwen2-7b-instruct-LoRA微调合并-ms-swift-单机单卡-V100(十三)

一、前言 本篇文章将使用ms-swift去合并微调后的模型权重,通过阅读本文,您将能够更好地掌握这些关键技术,理解其中的关键技术要点,并应用于自己的项目中。 二、术语介绍 2.1. LoRA微调 LoRA (Low-Rank Adaptation) 用于微调大型语言模型 (LLM)。 是一种有效的自适应策略,…

云架构:考量与框架

云架构&#xff1a;考量与框架 引言 在当今的数字化环境中&#xff0c;云计算已成为现代商业运营的基石。一个设计良好的云架构框架为可扩展、安全和弹性的系统奠定了基础。本文将深入探讨云架构的核心要素&#xff0c;讨论重要的考量因素、设计指南&#xff0c;以及最佳实践…