如果有两个线程访问全局变量,那么许多教程都说使变量volatile变为阻止编译器将变量缓存在寄存器中,从而无法正确更新.但是,访问共享变量的两个线程是通过互斥锁来调用保护的东西不是吗?但是在这种情况下,在线程锁定和释放互斥锁之间,代码处于一个关键部分,只有那个线程可以访问变量,在这种情况下变量不需要是volatile?
那么多线程程序中volatile的用途/目的是什么?
我阅读了一些关于该volatile关键字的文章,但我无法弄清楚它的正确用法.你能告诉我在C#和Java中它应该用什么吗?
在阅读JSR-133 Cookbook for Compiler Writers关于volatile的实现之后,特别是"与原子指令的交互"部分我认为读取volatile变量而不更新它需要LoadLoad或LoadStore屏障.在页面的下方,我看到LoadLoad和LoadStore在X86 CPU上实际上是无操作的.这是否意味着可以在x86上没有显式缓存失效的情况下完成易失性读取操作,并且正常变量读取速度快(忽略volatile的重新排序约束)?
我相信我不明白这一点.有人可以照顾我吗?
编辑:我想知道多处理器环境是否存在差异.在单CPU系统上,CPU可能会查看它自己的线程缓存,正如John V.所述,但在多CPU系统上,CPU必须有一些配置选项,这是不够的,主内存必须被击中,使得波动较慢在多CPU系统上,对吗?
PS:在我学习更多相关信息的路上,我偶然发现了以下很棒的文章,因为这个问题可能对其他人很有意思,我会在这里分享我的链接:
我读到了下面的某个地方.
Java volatile关键字并不意味着原子,它常见的误解是,在声明volatile之后,
++操作将是原子的,要使操作原子化,你仍然需要确保使用synchronizedJava中的方法或块进行独占访问 .
那么如果两个线程同时攻击一个volatile原始变量会发生什么呢?
这是否意味着,凡发生在它的锁,将要设置其值.如果在此期间,一些其他的线程来和读取旧值,而第一个线程正在改变它的价值,那么没有新的线程将读取其旧的价值?
Atomic和volatile关键字有什么区别?
我想编写一个程序,它可以直观地说明volatile关键字的行为.理想情况下,它应该是一个程序,它执行对非易失性静态字段的并发访问,并因此而获得不正确的行为.
在同一程序中添加volatile关键字应该可以解决问题.
那是我无法实现的.即使尝试多次,启用优化等,我总是会在没有'volatile'关键字的情况下获得正确的行为.
你对这个话题有什么看法吗?你知道如何在一个简单的演示应用程序中模拟这样的问题吗?它取决于硬件吗?
我读过" 什么时候在Java中使用'volatile'? "但我仍然感到困惑.我怎么知道何时应该标记变量volatile?如果我弄错了,要么在需要它的东西上省略volatile,要么在不需要的东西上放置volatile呢?在确定多线程代码中哪些变量应该是易变的时,有哪些经验法则?
我对volatile和mutable之间的区别有疑问.我注意到这两个都意味着它可以改变.还有什么?它们是一样的吗?有什么不同?它们适用于哪里?为什么提出这两个想法?如何以不同的方式使用它们?
非常感谢.
多个文本说,当在.NET中实现双重检查锁定时,您锁定的字段应该应用volatile修饰符.但为什么呢?考虑以下示例:
public sealed class Singleton
{
private static volatile Singleton instance;
private static object syncRoot = new Object();
private Singleton() {}
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
}
Run Code Online (Sandbox Code Playgroud)
为什么不"锁定(syncRoot)"完成必要的内存一致性?在"lock"语句之后,读取和写入都是不稳定的,因此必须实现必要的一致性,这不是真的吗?
我知道volatile通知编译器可能会更改值,但为了完成此功能,编译器是否需要引入内存栅栏才能使其工作?
根据我的理解,易失性对象的操作顺序不能重新排序,必须保留.这似乎暗示一些内存栅栏是必要的,并且没有真正解决方法.我说的是对的吗?
在这个相关问题上有一个有趣的讨论
...对于不同的volatile变量的访问不能由编译器重新排序,只要它们出现在单独的完整表达式中......对于线程安全而言volatile是无用的,但不是由于他给出的原因.这不是因为编译器可能会重新排序对易失性对象的访问,而是因为CPU可能会重新排序它们.原子操作和内存屏障阻止编译器和CPU重新排序
...从C++标准的角度来看,编译器执行某些操作与编译器发出导致硬件执行某些操作的指令之间没有区别.如果CPU可能重新排序对volatiles的访问,则标准不要求保留其订单....
... C++标准没有对重新排序有什么区别.你不能争辩说CPU可以重新排序它们没有可观察到的影响,所以没关系--C++标准将它们的顺序定义为可观察的.如果编译器生成的代码使平台能够满足标准要求,则编译器在平台上符合C++标准.如果标准要求对挥发物的访问不能重新排序,则重新排序它们的平台不符合要求....
我的观点是,如果C++标准禁止编译器重新排序对不同易失性的访问,理论上这种访问的顺序是程序可观察行为的一部分,那么它还要求编译器发出禁止CPU执行的代码所以.该标准没有区分编译器的作用以及编译器生成的代码使CPU执行的操作.
这确实产生了两个问题:它们中的任何一个是"正确的"吗?实际的实现到底做了什么?
是否允许编译器对此进行优化(根据C++ 17标准):
int fn() {
volatile int x = 0;
return x;
}
Run Code Online (Sandbox Code Playgroud)
这个?
int fn() {
return 0;
}
Run Code Online (Sandbox Code Playgroud)
如果是,为什么?如果没有,为什么不呢?
这里有一些关于这个主题的思考:当前编译器编译fn()为放在堆栈上的局部变量,然后返回它.例如,在x86-64上,gcc创建了这个:
mov DWORD PTR [rsp-0x4],0x0 // this is x
mov eax,DWORD PTR [rsp-0x4] // eax is the return register
ret
Run Code Online (Sandbox Code Playgroud)
现在,据我所知,标准并没有说应该将一个局部volatile变量放在堆栈上.所以,这个版本同样好:
mov edx,0x0 // this is x
mov eax,edx // eax is the return
ret
Run Code Online (Sandbox Code Playgroud)
这里,edx商店x.但是现在,为什么要停在这里?由于edx和eax均为零,我们可以只说:
xor eax,eax // eax is the return, and x as well
ret
Run Code Online (Sandbox Code Playgroud)
我们转变 …