为什么我们要使用volatile关键字?

25 浏览
0 Comments

为什么我们要使用volatile关键字?

可能是重复问题:
\n为什么要使用volatile关键字?\n我从来没有使用过它,但我想知道为什么人们要使用它?它到底有什么作用?我在论坛上搜索了一下,发现只有关于C#或Java的主题。

0
0 Comments

为什么我们使用volatile关键字?

在计算机编程中,特别是在C、C++和C#编程语言中,使用volatile关键字声明的变量或对象通常具有与优化和/或线程相关的特殊属性。一般来说,volatile关键字旨在防止编译器对假设变量值“自行更改”的代码应用任何优化。volatile关键字是一种类型限定符,用于声明一个对象可以被程序中的某些内容(如操作系统、硬件或并发执行的线程)修改。(c) MSDN C++ Reference.

volatile关键字与线程无关。

volatile关键字是一种类型限定符,用于声明一个对象可以被程序中的某些内容(如操作系统、硬件或并发执行的线程)修改。(c) MSDN C++ Reference。但是,msdn和维基百科肯定是错误的,你是正确的。

volatile关键字对线程没有帮助。volatile读取/写入仍然可以与非volatile读取/写入进行重排序,这使得它对线程目的毫无用处。此外,您可能已经注意到MSDN页面上有一个大大的“Microsoft Specific”。Microsoft对volatile的实现提供了超出标准规定的额外保证。所以,从技术上讲,MSDN是错误的。维基百科也有错误并不令人意外。

尽管如此,volatile的特性与线程有关。在多个线程之间共享的变量上使用volatile可以改变程序的语义。然而,volatile并不足以将语义更改为任何有用的或良好定义的内容。

我担心你不知道你在说什么。volatile关键字确保在一个线程中对变量的更改在所有线程中保持一致。通过根据需要插入屏障、同步点、缓存刷新、重新加载等平台相关的操作来实现。如果你不只是开发个人电脑,你可能会知道这一点。仅仅因为英特尔对缓存管理隐藏了很多东西,并不意味着它不会发生。

在Java中,volatile与线程有关。我相信在C++和C#中也有类似的影响。

但它的确有用...例如,当你关心内存是否是最新的,但并不完全准确时:一个缓存刷新机制读取由其他线程更新的“last_update”变量。你不在乎缓存稍后刷新几毫秒,但你需要“接近真实值”(请记住,在某些语言,如Java中,如果没有volatile,一个线程看到另一个线程所做的更改可能需要几秒钟或几分钟)。

volatile的可能用途很少,而且不太强大,但在这些受限制的情况下,volatile是可以的。原子变量更好,但volatile也可以。

“实际上,你不知道你在说什么。”实际上,你才是那个不知道你在说什么的人;哪个编译器在哪个系统上的哪种语言为哪个操作插入了缓存刷新呢?

我并不是指涉及缓存刷新,而是指插入围栏或内存管理技术。在Java中,内存模型确保所有线程看到变量的一致值,防止其他线程读取旧值(可能被缓存)。在C++中,这取决于平台,但某些架构显然会引入围栏,你是对的,似乎与多线程无关。在C#中,volatile与多进程相关。

确实在Java中,volatile是线程之间的通信工具,对这些标量的写入具有特定的含义。在C和C++中,volatile主要是用于MMIO(扩展卡、驱动程序等)、异步信号、特殊伪函数setjmp,还有其他与线程无关的用途。C和C++中的标准支持线程间通信是通过原子对象(在C++中拼写为std::atomic)实现的。它们的语义完全不同。

通过哪些指令来实现这些功能?

这是一个关于C++的问题,我的评论是关于Java和C#的(一旦被证明我对C++的理解是错误的),所以我们不应该在这个线程上混淆。无论如何,对于Java,请阅读这个和这个,在这里有一个经典示例:stackoverflow.com/questions/11595868/...。对于C#,使用Google。

0
0 Comments

为什么我们要使用volatile关键字?

考虑以下代码,

int some_int = 100;
while(some_int == 100)
{
   //你的代码
}

当编译这个程序时,如果编译器发现程序从来没有试图改变some_int的值,那么它可能会对这段代码进行优化,将while循环从while(some_int == 100)改为等价于while(true)的形式,以便执行速度更快(因为while循环的条件看起来始终为true)。如果编译器不进行优化,那么它就需要在每次迭代中获取some_int的值并与100进行比较,这显然会比较慢。

然而,有时候优化(程序的某些部分)可能是不可取的,因为可能有其他人从程序之外改变了some_int的值,而编译器并不知道,因为它看不到;但这是你设计的方式。在这种情况下,编译器的优化将无法产生期望的结果!

因此,为了确保期望的结果,你需要通过某种方式阻止编译器优化while循环。这就是volatile关键字发挥作用的地方。你只需要这样做,

volatile int some_int = 100; //注意现在有了'volatile'修饰符!

插入引用块:

volatile告诉编译器,

"嘿,编译器,我是易变的,你知道,我可以被一些你甚至不知道的XYZ改变。那个XYZ可以是任何东西。也许是这个星球之外叫做程序的外星人。也许是闪电,某种形式的中断,火山等等可以改变我的。也许。你永远不知道谁会改变我!所以,噢,你这个无知的人,不要扮演一个无所不知的上帝,不要冒然触碰我所在的代码。好吗?"

这就是volatile如何阻止编译器优化代码的方式。现在搜索网络以查看一些示例代码。

引用C++标准($7.1.5.1/8)中的内容:

[..]volatile是一个提示给实现的,要求它避免使用与对象相关的激进优化,因为对象的值可能通过一些实现无法检测到的方式进行更改。[...]

相关主题:

使结构体变量volatile是否会使其所有成员变量都变为volatile?

需要强调的是,volatile是一个限定符,类似于const(但意义不同),因此你也可以声明只能在volatile实例上调用的volatile方法。

我认为这就是extern关键字的作用吗?

:不。 extern关键字用于其他用途。

根据你的例子,编译器在这里对代码进行了很好的优化,因为我猜测在while函数内部它没有改变some_int的值。你能否也解释一下可能发生some_int值更改的场景?如果没有,那么在这个例子中使用volatile有什么用?

很好的答案,可以提到在示例中,some_int的值可以被另一个线程或中断例程改变,这是代码将失败的两个示例,只是为了澄清。(-这应该回答了你的评论)

因此,由于值可能从程序之外被更改,缓存变量的内容也会被volatile使用禁用吗?

不明白,这是否意味着如果程序员忘记在应该使用"volatile"的地方使用它,编译器可能会生成错误的二进制可执行文件?有人能提供一个验证这个概念的示例代码吗?谢谢。

:我不认为有人有时间来编写一个验证这个概念的示例代码,因为那是一项困难的任务。请注意,没有volatile并不保证变量的加载会被优化掉。只是volatile存在确保它不会被优化掉,即使在没有关键字的情况下也很可能发生这种情况;不同之处在于volatile不存在不保证这一点。

一个程序可以访问另一个程序的内存吗?

是的,一个程序可以访问另一个程序的内存。一个例子是操作系统可以访问任何它想要的内存,无论它是否属于它。另一个例子是两个访问由mmap创建的共享缓冲区的程序,例如。

我在过去的某个地方读到volatile是为了确保一个变量占据一个内存空间,并且不会被编译器优化掉。对我来说,这个意义对volatile的很多可能用途都有意义。我猜,volatile现在在内部更高级,不总是需要实际的内存空间来存储变量。是这样吗?请帮我澄清一下。

:如果它不需要实际的内存空间,那么将存储在哪里?以这样的方式思考:volatile int val = 10;,那么&val指向一个有效的地址吗?

,所以可以说,即使在当前的内部实现中,volatile始终为变量提供内存空间吗?我读过stackoverflow上关于volatile影响的几个答案和评论,让我好奇和困惑的是,他们只说"编译器不会优化掉",而不是自信地说"它确保为变量提供内存空间"。所以我想可能现在有一些更多/替代的东西在volatile内部,除了给它提供"内存空间",仍然可以实现"易变性"。

,是的,这就是我从我在这里的第一条评论中担心的。多年来,我相信volatile是为了确保变量在内存中,而这对我很有帮助。但是最近,包括在这个stackoverflow页面上,我没有读到任何一个自信地说volatile变量将占据内存空间,而是像:“编译器不会优化掉”,让我想:“也许现在有一些其他的东西了”。我希望你能理解我的观点,我觉得我们在“不同的页面”。但是尽管我读过的所有东西,我想我还是会选择我所知道的volatile的旧含义。

提供volatile所需的语义可能需要确保变量在某个特定平台上具有内存空间。对于已经存在或将来可能存在的所有平台,无法知道这是否是必需的。哪怕可以想象一个只有CPU寄存器的平台,只有很多很多寄存器。

volatile用于修复在没有一致内存的系统上出现的许多问题,比如PS3、XboxOne等。要完全理解volatile关键字,必须了解一致内存和非一致内存的区别。

通过ptrace或类似的方式,在支持程序调试的任何操作系统上访问另一个程序的内存。volatile保证在停止执行到一个序列点时(也可以使用ptrace停止线程),可以检查变量的值(它按照ABI表示)甚至进行修改。

另一个线程也可以是"你甚至不知道的一些XYZ"吗?

:是的。可能是!

你是否可以分享一个以下情况的示例:“...来自编译器不知道的程序外部更改了some_int的值,因为它看不到,但这是你设计的方式...”?

哦,你应该写一本令人愉快的C++圣经

这个答案是错误的。你给出的例子中行为的改变是由翻译器进行的语法树优化引起的(不是由标准指定的)。此外,标准明确指出这种行为不被保证(标记为volatile的变量将在存储后进行加载)。你引用的标准部分与抽象执行引擎有关,它有一个非常明确的一组优化,编译器可以执行(gcc/clang不遵守volatile关键字的限制,并且大多数情况下将其委托给-O标志)。

"标记为volatile的变量将在存储后进行加载"。哪部分规范说了这个?

我的意思是,标记为volatile的变量将在存储后进行加载,而不是在寄存器中重用值。至于标准,在C11第6.7.3节中,参见第7款。最初它声明:“此外,在每个序列点上,对象中最后存储的值应符合抽象机器所规定的值,除非被之前提到的未知因素修改”-大多数编译器将其解释为每次都进行加载。紧接着它又声明对volatile的访问是实现定义的(所以你的解释可能不同)。

:我不明白你对这个答案的反驳是什么。我也发现很难将你的说法与你从规范中引用的文本联系起来。你能否详细解释一下?或者发表一个答案?或者在github上发布一个gist并在这里发布链接?

当阻止编译器执行某些优化时,如果这些优化恰好可以由CPU、内存控制器、操作系统、系统库、驱动程序或系统的任何其他组件执行,那么这有什么帮助呢?由于volatile不能保证阻止编译器进行优化的任何特定效果,除非在提供实际保证的特定平台上使用,否则它有什么用处?

0