C++学习之路(三):volatile关键字

news/2024/7/7 12:35:56

volatile是c++中的一个关键字。用volatile修饰的变量,具有三个性质:易变性

 

(一)易变性:

由于编译器对代码执行的优化,两条赋值语句,下一条语句可能会直接从上一条语句使用的寄存器中取得变量内容,通过volatile修饰变量,使得变量将被写会内存,对变量的访问也会直接从内存中读取,而不是从寄存器中获取。

测试代码1:

上述代码中,a为非volatile变量,b=a+1,对应的汇编代码为lea ecx, [eax + 1]。由于前一条语句变量a的值被保存在寄存eax中,因此b=a+1,可以直接从寄存器eax中读取值来进行计算。

测试代码2:

变量a为volatile变量,在a=fn(c)被执行后,寄存器ecx中的内容被写回到内存:mov dword ptr [esp+0Ch], ecx,在执行b=a+1时,变量a将直接从内存中进行读取,而不是使用寄存器ecx中的值。

小结:易变性。从汇编层次反应,就是两条语句,下一条语句不会直接使用上一条语句对应的volatile变量的寄存器内容,而是直接从内存中读取。

 

(二)不可优化性

测试代码3:

上述代码由于编译器优化,a,b,c三个变量都是可以通过常量进行替代,最终汇编代码更加简洁。

测试代码4:

由于a,b,c变量都通过volatile修饰,在汇编层面上,编译器将不会进行优化,最终将变量直接从内存读入到寄存器。

小结:不可优化性。volatile告诉编译器,不要对该修饰变量进行优化,保证写在代码中的指令一定会被执行。

 

(三)顺序性

对于多线程需要被同时访问或修改的变量,为了防止并发访问修改全局变量由于编译器优化带来的问题,常常会加上volatile关键词,来防止编译器进行不必要的优化。

首先给出一个样例代码,之后再来进行讨论:

在上述样例代码中,thread1线程在执行了something赋值后,设置flag为true,然后在thread2线程中判断当前flag为true,表明当前something已经执行完毕,接下来开始执行other things。这里通过if (flag==true)来表示something已经执行完毕,实际情况是不是这样的?先看几个测试代码,再来讨论该场景。

测试代码5:

上述中A,B均为非volatile变量,在函数执行过程中,可以看到通过gcc编译器的优化,两条语句的执行顺序被交换了,即先执行了B=0,再执行了A=B+1,编译器会保证在函数结束之后最终结果没有影响,但实质上可以看出编译器可能会对函数内部指令进行优化

测试代码6:

这里将一个变量B用volatile进行修饰,但对于编译器来说,实际并没有阻止优化发生,指令依然是乱序执行的。如此:volatile变量,与非volatile变量之间的操作,是可能被编译器优化从而交互顺序执行的。实际上,从这个测试代码也可以看出最初的场景,尽管将flag声明为volatile变量,但由于编译器优化可能导致flag先于something被执行,从而导致切换到thread2线程时,flag判断为true,但并未执行something,从而导致程序逻辑混乱。

测试代码7:

从上述代码中可以看出,当两个变量都被volatile进行修饰时,编译器高度顺序执行了对应指令,并没有进行优化导致乱序执行。如此:volatile变量之间的操作,是不会被编译器交互顺序执行的

回到最初的那个场景,为了保证something和flag的执行顺序,根据测试代码7,将两个变量都通过volatile,是否可以防止乱序执行的产生?

答案是不行!尽管这样的方法可以避免编译器优化,产生出顺序执行的机器代码,但是最终机器指令是通过CPU来完成执行的,在现代CPU中,本身就已经对指令执行做了很多优化,指令乱序执行就是其提高效率的一种方法,所以将两个变量都声明为volatile,仍然不能保证CPU执行指令时是顺序执行的

一个正确的做法是:

 其实只用保证在thread1中的代码,是先于thread2中的代码执行即可。最为常用的方法,采用互斥锁机制,对执行代码进行加锁,在执行完后进行解锁

小结:顺序性。volatile变量之间的顺序性,编译器不会进行乱序优化。volatile与非volatile变量之间的顺序,编译器可能会进行优化,不保证顺序。同时,在进行多线程编程时,需要小心使用volatile,最为常用方式是通过互斥锁保证对临界区的访问。

部分内容引用自:https://www.cnblogs.com/god-of-death/p/7852394.html

转载于:https://www.cnblogs.com/scu-cjx/p/8601658.html


http://www.niftyadmin.cn/n/543245.html

相关文章

语法基础——Proguard语法基础

转自:https://blog.csdn.net/qq_30379689/article/details/81589428 启用混淆 通过工程下的build.gradle文件中的开启混淆开关和配置混淆规则文件 minifyEnabled:混淆开关 proguard-android.txt:SDK中默认proguard的配置规则 proguard-rule…

JS 引用类型和值类型

自己的看高程3第五章应用类型的时候,有些迷糊,所以上网搜到这篇文章 转自求小天的博客园,地址:http://www.cnblogs.com/lxq1990/archive/2012/11/04/2754226.html 这个比喻很形象,帮助理解。 js 值类型和引用类型 Java…

服务器raid5磁盘阵列不同故障的数据恢复方法列举和raid磁盘阵列分析

服务器Raid 5磁盘阵列算法原理 分布式奇偶校验的独立磁盘结构(也就是我们称之为的raid 5)数据恢复有一个“奇偶校验”概念需要理解。我们可以把它简单的理解成为二进制运算中的“异或运算”,通常使用的标识是xor。这个用运算的规则就是若二者…

Service生命周期

与Activity类似,Service也有自己的生命周期函数,在不同的时刻,系统会调用对应的Service生命周期函数,不过与Activity声明周期相比,Service的声明周期更加简单,我们通过官方给出的一张图片来体会一下&#x…

C#/Sqlite-单机Window 程序 sqlite 数据库实现

数据库分析和选择 Excel 文件 做数据源 限制性比较强,且不适合查询,分析 等操作 Access 数据库 Access 管理数据界面和功能不强 mysql 和sql server 功能满足,但需要安装 最后 还是选择sqlite 数据库 C#中sqlite数据库实现 ste…

Tomcat杂记(1)

Tomcat Tomcat基础 1、安装jdk 1.1 安装jdk [rootmaster1 tomcat]# ls apache-tomcat-8.0.41.tar.gz jdk-8u121-linux-x64.rpm安装jdk [rootmaster1 tomcat]# rpm -ivh jdk-8u121-linux-x64.rpm 1.2 设置java环境变量 [rootmaster1 jdk1.8.0_121]# vim /etc/profile.d/java.sh…

Mac Android Apk反编译

转自:https://www.cnblogs.com/typing/p/7780017.html 在mac os系统上反编译android apk,首先需要准备好以下3个文件: 1、apktool:https://ibotpeaches.github.io/Apktool/install/ 2、dex2jar:https://github.co…

通过使用浏览器对象模型,输出当前浏览器窗口中打开的文档的URL信息,并将显示在窗口中。...

<script type"text/javascript">window.document.write("这个网页文件来自&#xff1a;".bold());window.document.write(window.location.toString());</script>转载于:https://www.cnblogs.com/clear93/p/4624174.html