【cc++】volatile关键字的作用

​​volatile​​描述

​​volatile​​ 是C和C++都支持的一个关键字,是一种类型修饰符。这个关键字被设计用来告诉编译器,一个变量可能会在程序之外被改变,例如,它可能被中断服务程序修改,或者它可能映射到一个硬件寄存器,这个寄存器的值可能由硬件改变。因此,编译器不应对涉及volatile变量的操作进行优化,因为这些优化可能会假设变量的值在两次访问之间不会改变。

需要注意的是,volatile并不能保证操作的原子性。在多线程环境中,如果一个volatile变量被同时修改,仍然可能会发生数据竞争。因此,在多线程编程中,std::atomic通常是一个更好的选择,因为它不仅防止了编译器的优化,还提供了原子性和内存一致性的保证。

​​volatile​​作用

以下是volatile的主要作用:

  1. 防止编译器优化:遇到volatile关键字声明的变量,编译器对访问该变量的代码不再进行优化,可以提供对特殊地址的稳定访问。
  2. 确保数据一致性:被volatile修饰的变量,系统每次用到它时,都是直接从对应的内存中提取,而不会利用缓存。这样就防止了多线程操作同一变量时,由于缓存导致的数据不一致性问题。

​​volatile数据操作示例

volatile示例,多个线程将会对同一个volatile变量进行操作:

#include <iostream>  
#include <thread>  
#include <atomic>  
#include <chrono>  
#include <vector>  // 全局的volatile变量  
volatile int shared_data = 0;  // 一个线程将会执行的任务  
void increment(int n) {  for (int i = 0; i < n; ++i) {  ++shared_data;// 休眠一段时间来模拟复杂操作std::this_thread::sleep_for(std::chrono::milliseconds(1));  }  
}  int main() {  const int num_threads = 5;  const int num_increments = 100;  std::vector<std::thread> threads;  // 创建并启动多个线程  for (int i = 0; i < num_threads; ++i) {  threads.push_back(std::thread(increment, num_increments));  }  // 等待所有线程完成  for (auto& thread : threads) {  thread.join();  }  // 输出volatile变量的值  std::cout << "shared_data: " << shared_data << std::endl;  return 0;  
}

以上代码作用:
示例中,定义一个全局的volatile变量​​shared_data​​,多个线程将会同时对这个变量进行增加操作。

每个线程将会对​​shared_data​​​进行​​num_increments​​次增加操作,每次增加操作后,线程将会休眠一段时间来模拟复杂的操作。
主线程将会等待所有子线程完成后,输出​​shared_data​​的值。

由于​​shared_data​​​是volatile的,所以每个线程在读取它的值时,都会直接从内存中读取,而不是从自己的缓存中读取,这就保证了所有线程在任何时候看到的​​shared_data​​的值都是最新的。

输出结果变化原因及解决方案

如果每个线程都正确地增加了​​shared_data​​变量的值,那么最终的输出应该是500(5个线程,每个线程增加100次,总共增加500次)。然而,这个程序的输出可能每次都不同,这是因为多个线程可能同时对​​shared_data​​变量进行操作,导致数据竞争(data race)的问题。

但每次输出,结果不是500,每次都有变化,原因是:
数据竞争发生在至少一个线程正在写入一个内存位置,并且至少有一个其他线程正在读取或写入同一个内存位置,并且这两个操作中的至少一个是未同步的。在这种情况下,读取操作可能会读取到一个中间值,这个值是由两个写入操作的部分结果组成的。这就是为什么即使每个线程都正确地增加了​​shared_data​​的值,最终的输出可能仍然是不正确的。

在C++中,volatile关键字并不能保证操作的原子性。即使​​shared_data​​变量是volatile的,一个线程在读取它的值时可能会被另一个线程的写入操作打断,导致读取到一个中间值。这就是为什么volatile关键字不能保证在多线程环境中正确地同步数据。
要解决这个问题,可以使用C++11引入的std::atomic库。std::atomic库提供了一种在多线程环境中安全地操作数据的方法。

以下是使用std::atomic优化后的例子:

#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>
#include <vector>// 全局的std::atomic变量
std::atomic<int> shared_data(0);// 线程将会执行的任务
void increment(int n) {for (int i = 0; i < n; ++i) {// 使用fetch_add方法原子地增加shared_data的值shared_data.fetch_add(1);// 休眠一段时间来模拟复杂操作std::this_thread::sleep_for(std::chrono::milliseconds(1));}
}int main() {const int num_threads = 5;const int num_increments = 100;std::vector<std::thread> threads;// 创建并启动多个线程for (int i = 0; i < num_threads; ++i) {threads.push_back(std::thread(increment, num_increments));}// 等待所有线程完成for (auto& thread : threads) {thread.join();}// 输出std::atomic变量的值std::cout << "shared_data: " << shared_data << std::endl;return 0;
}

示例中,​​shared_data​​变量被声明为std::atomic,并且使用fetch_add方法原子地增加它的值。这样,即使有多个线程同时对​​shared_data​​进行操作,也不会发生数据竞争的问题,因为每个操作都是原子的。这将确保每次程序的输出都是500

分析:volatilestd::atomic 的区别

std::atomicvolatile在内存模型上有一些不同。

首先,std::atomicC++11中引入的,设计用来解决多线程数据竞争问题的工具。它提供了强类型的原子操作,包括load, store, exchange, compare_exchange_strong等,这些都是线程安全的。这意味着,当你在多线程环境下对一个std::atomic变量进行操作时,这些操作是不可中断的,即它们是原子的。因此,不会出现一个线程正在写入数据,而另一个线程读取到的是部分写入的数据这种情况。

至于std::atomic变量的读取操作是否直接从内存中读取数据,还是从线程的缓存中读取,这实际上取决于具体的实现和硬件架构。在大多数情况下,为了提高性能,现代处理器通常会使用缓存来存储最近访问的数据。当一个线程尝试读取一个std::atomic变量时,如果这个变量的值已经在该线程的缓存中,那么该线程可能会直接从缓存中读取这个值,而不是从内存中读取。然而,如果其他线程已经修改了这个变量的值,并且这个新的值还没有被当前线程缓存,那么当前线程将会从内存中读取这个新的值。这个过程是由硬件和操作系统自动管理的,对于程序员来说是透明的。

另一方面,volatile关键字告诉编译器不要优化涉及这个变量的操作,但并不保证操作的原子性。也就是说,如果一个volatile变量在多线程环境中被同时修改,仍然可能会发生数据竞争。而且,volatile并不能保证变量的值一定会从内存中读取,而不是从线程的缓存中读取。
通常来说,如果你已经使用了std::atomic,那么通常不需要再额外使用volatilestd::atomic已经提供了你需要的所有保证。

因为std::atomic已经提供了原子性和内存一致性的保证。原子性确保了操作是不可中断的,即它们要么完全执行,要么完全不执行。内存一致性保证了所有线程看到的变量值是一致的。这意味着,当一个线程修改了一个std::atomic变量的值,其他线程将会立即看到这个新的值,而不管它们是否有自己的缓存。

因此,通常建议只使用std::atomic来处理多线程环境中的共享变量。

volatile关键字用法示例

对寄存器进行赋值时,可以使用volatile关键字。volatile关键字告诉编译器,变量的值可能会在程序之外被改变,因此编译器不应对涉及该变量的操作进行优化。

在某些情况下,寄存器的值可能会被硬件或其他中断服务程序修改。如果在程序中访问这样的寄存器,并且希望每次访问都能得到最新的值,那么可以使用volatile关键字来声明该寄存器变量。

需要注意的是,volatile并不能保证操作的原子性。如果有多个线程或中断服务程序同时修改同一个寄存器变量,仍然可能会发生数据竞争。在这种情况下,需要使用其他同步机制,例如锁或原子操作,来确保操作的正确性和一致性。

下面使用volatile关键字对寄存器进行赋值示例:

#include <iostream>
#include <thread>
#include <chrono>// 假设我们有一个硬件寄存器,它的地址是0x12345678
#define REGISTER_ADDRESS 0x12345678// 声明一个volatile指针,指向该寄存器
volatile unsigned int* registerPtr = (volatile unsigned int*)REGISTER_ADDRESS;int main() {// 启动一个线程,不断读取寄存器的值并输出std::thread readerThread([]{while (true) {unsigned int value = *registerPtr; // 读取寄存器的值std::cout << "Register value: " << value << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1)); // 每隔1秒读取一次}});// 主线程不断修改寄存器的值unsigned int count = 0;while (true) {*registerPtr = count++; // 对寄存器进行赋值std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 每隔500毫秒修改一次}// 等待读取线程结束(实际上这个程序不会正常结束,需要手动停止)readerThread.join();return 0;
}

示例中,假设有一个硬件寄存器,它的地址是​​0x12345678​​。

之后声明了一个volatile unsigned int*​​​类型的指针​​registerPtr​​,指向该寄存器的地址。

这样,通过对​​registerPtr​​进行解引用,可以读取或修改该寄存器的值。

​​main​​​函数中,启动了一个线程​​readerThread​​​,它不断读取寄存器的值并输出到控制台。主线程则不断修改寄存器的值。由于​​registerPtr​​​被声明为​​volatile​​,编译器不会对涉及该指针的操作进行优化,确保了每次读取和修改都能得到最新的值。

结论

万事开头难,然后中间难,最后结尾难

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

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

相关文章

使用Inno Setup 打包程序文件 怎么把其中一个文件安装时复制到指定系统文件夹

环境: Inno Setup 6.6 Win10 专业版 问题描述: 使用Inno Setup 打包程序文件 怎么把其中一个文件安装时复制到指定系统文件夹 将文件api-ms-win-shcore-scaling-l1-1-1.dll复制到system32里面 解决方案: 1.由于安全和权限的限制,直接在Inno Setup脚本中复制文件到C:\…

C++新经典模板与泛型编程:用成员函数重载实现std::is_class

用成员函数重载实现is_class std::is_class功能&#xff0c;是一个C11标准中用于判断某个类型是否为一个类类型&#xff08;但不是联合类型&#xff09;的类模板。当时在讲解的时候并没有涉及std::is_class的实现代码&#xff0c;在这里实现一下。简单地书写一个IsClass类模板…

python pydoc生成API文档

pydoc是python内置的一个文档生成模块。 pydoc 模块会根据 Python 模块来自动生成文档。 生成的文档可在控制台中显示为文本页面&#xff0c;提供给 Web 浏览器访问或者保存为 HTML 文件。 对于模块、类、函数和方法&#xff0c;显示的文档内容取自文档字符串&#xff08;即 _…

(Note)Chromium浏览器插件

Chromium浏览器插件 1.TamperMonkey Home | Tampermonkey 2.GreasyFork Greasy Fork - 安全、实用的用户脚本大全

泰凌微(Telink)8258配置串口收发自定义数据

在官网下载SDK后&#xff08;以Mesh SDK为例&#xff09;使用Eclipse打开&#xff0c;对应MCU的配置文件在app_config_8258.h&#xff0c;默认的HCI接口是HCI_USE_NONE&#xff0c;如果改成HCI_USE_UART后可以通过串口收发数据&#xff0c;此时默认接收函数处理的是以Telink的协…

索引的优缺点是什么

数据是存储在磁盘上的&#xff0c;操作系统读取磁盘的最小单位是块&#xff0c;如果没有索引&#xff0c;会加载所有的数据到内存&#xff0c;依次进行检索&#xff0c;加载的总数据会很多&#xff0c;磁盘IO多。如果有了索引&#xff0c;会以某个列为key创建索引&#xff0c;M…

音视频学习(二十)——rtsp收流(udp方式)

前言 本文主要介绍通过udp方式实现rtsp拉流。 流程图 流程说明&#xff1a; 相较于tcp方式“信令数据”复用同一连接拉流&#xff0c;udp方式拉流“信令数据”采用不同的连接&#xff0c;信令传输采用tcp&#xff0c;流数据传输采用udp&#xff1b;客户端向服务端&#xff0…

数据库增删改查(CRUD)进阶版

目录 数据库约束 约束类型 表的设计 1.一对一 2.一对多 3.多对多 增删查改进阶操作 1. 插入查询结果 2.查询 聚合查询 聚合函数 group by having 联合查询 内连接 外连接 自连接 子查询 合并查询 数据库约束 创建表的时候制定的一些规则&#xff0c;在后续…

在Windows 10或11中,复制和粘贴不起作用,不一定是键盘的问题

以下建议将帮助你解决复制和粘贴无法正常工作的问题。 以下提示主要适用于Windows 10和Windows 11,但也可能解决旧版本Windows上的复制和粘贴问题。 为什么我的复制粘贴不起作用 复制和粘贴不起作用的问题可能以以下方式之一出现。 其他正在运行的应用程序或进程可能会使用…

智能优化算法应用:基于北方苍鹰算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于北方苍鹰算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于北方苍鹰算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.北方苍鹰算法4.实验参数设定5.算法结果6.参考…

生成式人工智能笔记-AIGC笔记

生成式人工智能笔记-AIGC笔记 十多年前&#xff0c;人工智能还只是一个不被人看好的小众领域&#xff0c;但是现在&#xff0c;它却已经成了街头巷尾的热点谈资&#xff0c;几乎任何事情都可以和人工智能联系在一起。 人工智能包括基础层、技术层和应用层。 基础层是人工智能…

收藏!当今最流行的10 种人工智能算法

人工智能的概念始于1956年的达特茅斯会议&#xff0c;由于受到数据、计算力、智能算法等多方面因素的影响&#xff0c;人工智能技术和应用发展经历了多次高潮和低谷。 2022年以来&#xff0c;以ChatGPT为代表的大模型一夜爆火&#xff0c;它能够基于在预训练阶段所见的模式和统…

Python中如何判断List中是否包含某个元素

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python中&#xff0c;判断一个列表&#xff08;List&#xff09;是否包含某个特定元素是常见的任务之一。在本文中&#xff0c;将深入探讨多种判断List成员包含性的方法&#xff0c;并提供丰富的示例代码&…

二、C#笔记

/// <summary> /// 第三章&#xff1a;方法和作用域 /// </summary> namespace Chapter3 { class Program { public static void Main(string[] args) { //3.1创建方法 Console.WriteLine($"{addValues(1, 2…

每日一题:LeetCode-11.盛水最多的容器

每日一题系列&#xff08;day 13&#xff09; 前言&#xff1a; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f50e…

UVa133救济金发放题解

题目 题目描述 n(n<20)个人站成一圈&#xff0c;逆时针编号为 1~n。有两个官员&#xff0c;A从1开始逆时针数&#xff0c;B从n开始顺时针数。在每一轮中&#xff0c;官员A数k个就停下来&#xff0c;官员B数m个就停下来&#xff08;两个官员有可能能停在同一个人上&#xff…

solidity案例详解(六)服务评价合约

有服务提供商和用户两类实体&#xff0c;其中服务提供商部署合约&#xff0c;默认诚信为true&#xff0c;用户负责使用智能合约接受服务及评价&#xff0c;服务提供商的评价信息存储在一个映射中&#xff0c;可以根据服务提 供商的地址来查找评价信息。用户评价信息&#xff0c…

添加新公司代码的配置步骤-Part1

原文地址&#xff1a;配置公司代码 概述 我们生活在一个充满活力的时代&#xff0c;公司经常买卖子公司。对于已经使用 SAP 的公司来说&#xff0c;增加收购就成为一个项目。我开发了一个电子表格&#xff0c;其中包含向您的结构添加新公司代码所需的所有配置更改。当然&…

c++算术生成算法二

#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<functional> #include<vector> #include<algorithm> #include<numeric>//算术生成算法头文件 #include<string> #include<ctime> using namespace std; set_intersec…

虚拟数据优化器VDO

本章主要介绍虚拟化数据优化器。 什么是虚拟数据优化器VDO创建VDO设备以节约硬盘空间 了解什么是VDO VDO全称是Virtual Data Optimize&#xff08;虚拟数据优化)&#xff0c;主要是为了节省硬盘空间。 现在假设有两个文件file1和 file2&#xff0c;大小都是10G。file1和 f…