正如我最近发布的this answer中所展示的,我似乎对volatile
在多线程编程上下文中的实用程序(或缺少实用程序)感到困惑。
我的理解是:任何时候在访问变量的代码的控制流之外更改变量时,都应该将该变量声明为volatile
。信号处理程序、I/O寄存器和由另一个线程修改的变量都构成了这种情况。
因此,如果您有一个全局int foo
,并且foo
由一个线程读取并由另一个线程以原子方式设置(可能使用适当的机器指令),则读取线程看到这种情况的方式与它看到由信号处理程序调整或由外部硬件条件修改的变量的方式相同,因此foo
应该被声明为volatile
(或者,对于多线程情况,使用内存隔离加载访问,这可能是一个更好的解决方案)。
我怎么错了,哪里错了?
发布于 2010-03-21 06:19:57
我不认为你错了--易失性是必要的,以保证线程A会看到值的变化,如果值被线程A以外的其他东西改变了。据我所知,易失性基本上是一种告诉编译器“不要在寄存器中缓存这个变量,而是在每次访问时总是从RAM内存中读取/写入它”的一种方法。
造成混淆的原因是,volatile不足以实现许多事情。特别是,现代系统使用多级缓存,现代多核CPU在运行时进行一些奇特的优化,现代编译器在编译时进行一些奇特的优化,这些都会导致各种副作用以不同的顺序出现,如果您只查看源代码的话。
因此,易失性是很好的,只要您记住,易失性变量中的“观察到”的变化可能不会在您认为它们会发生的确切时间发生。具体地说,不要尝试使用易失性变量作为跨线程同步或排序操作的方法,因为它不会可靠地工作。
就我个人而言,我的main (唯一?)用于易失性标志的是一个"pleaseGoAwayNow“布尔值。如果我有一个不断循环的工作线程,我会让它在每次循环迭代时检查易失性布尔值,如果布尔值为真,就退出。然后,主线程可以通过将布尔值设置为true,然后调用pthread_join()等待,直到工作线程消失,从而安全地清理工作线程。
发布于 2010-03-21 06:43:18
你的理解真的是错的。
易失性变量所具有的属性是“读取和写入此变量是程序可感知行为的一部分”。这意味着这个程序可以工作(给定适当的硬件):
int volatile* reg=IO_MAPPED_REGISTER_ADDRESS;
*reg=1; // turn the fuel on
*reg=2; // ignition
*reg=3; // release
int x=*reg; // fire missiles
问题是,这不是我们想要的线程安全属性。
例如,线程安全计数器应该是(linux-kernel-like代码,不知道c++0x的等价物):
atomic_t counter;
...
atomic_inc(&counter);
这是原子的,没有内存障碍。如有必要,您应该添加它们。添加volatile可能不会有帮助,因为它不会将访问关联到附近的代码(例如。为了将元素附加到该列表,计数器正在计数)。当然,你不需要在程序之外看到计数器递增,优化仍然是可取的,例如。
atomic_inc(&counter);
atomic_inc(&counter);
仍然可以优化为
atomically {
counter+=2;
}
如果优化器足够智能(它不会改变代码的语义)。
发布于 2014-08-02 09:54:27
这就是“易失性”所做的一切:“嘿,编译器,这个变量随时都可能改变(在任何时钟节拍上),即使没有本地指令作用于它,也不要在寄存器中缓存这个值。”
就是这样。它告诉编译器你的值是易失性的--这个值可以随时被外部逻辑(另一个线程、另一个进程、内核等)改变。它的存在或多或少只是为了抑制编译器优化,因为编译器优化会在寄存器中默默地缓存一个值,而这些值本来就是不安全的。
您可能会遇到像"Dr. Dobbs“这样的文章,这些文章将易失性作为多线程编程的灵丹妙药。他的方法并不是完全没有优点,但它有一个根本的缺陷,即让对象的用户对其线程安全负责,这往往与其他违反封装的问题相同。
https://stackoverflow.com/questions/2484980
复制相似问题