我正在编写一个C ++应用程序。
我有一个多个线程正在写入的类变量。
在C ++中,任何可以在没有编译器“意识到”它被更改的情况下进行修改的东西都需要标记为volatile吗?因此,如果我的代码是多线程的,并且一个线程可以写入var而另一个线程从中读取,我是否需要标记var volaltile?
[我没有竞争条件,因为我依赖于对原子的写入]
谢谢!
答案 0 :(得分:13)
C ++还没有任何多线程规定。在实践中,volatile不能达到你的意思(它是专为内存加入的硬件而设计的,虽然两个问题相似但它们不同,volatile不能做正确的事情 - 请注意,volatile已用于其他mt上下文中的用法语言。)
因此,如果您想在一个线程中编写一个对象并在另一个线程中读取它,则必须在需要时使用实现所需的同步功能。对于我所知道的人来说,挥发性没有发挥作用。
仅供参考,下一个标准将考虑到MT,并且volatile将不起作用。所以这不会改变。您将只具有需要同步的标准定义条件以及实现它们的标准定义方式。
答案 1 :(得分:4)
是的,不稳定是你需要的绝对最低限度。它确保代码生成器不会生成将变量存储在寄存器中的代码,并始终对内存执行读写操作。大多数代码生成器可以为与本机CPU字大小相同的变量提供原子性保证,它们将确保内存地址对齐,以便变量不会跨越缓存行边界。
然而,这对于现代多核CPU来说并不是一份非常强大的合同。易失性不承诺在另一个核心上运行的另一个线程可以看到变量的更新。这需要一个内存屏障,通常是一个刷新CPU缓存的指令。如果您没有提供屏障,线程将继续运行,直到自然发生这种冲洗。最终会发生这种情况,线程调度程序必然会提供一个。这可能需要几毫秒。一旦你处理了这样的细节,你最终会重新发明一个条件变量(也就是事件),它不可能比线程库提供的更快。或者经过测试。不要发明自己的,线程很难做到正确,你不需要不确定基本原语是否可靠的FUD。
答案 2 :(得分:3)
volatile指示编译器不要优化变量值或用法的“直觉”,因为它可以“从外部”进行优化。
volatile不会提供任何同步,但是你对int的写入假设是原子的,这几乎是现实的!
我猜我们需要查看一些用法,以了解您的情况是否需要volatile(或检查程序的行为),或者更重要的是,如果您看到某种同步。
答案 3 :(得分:1)
我认为volatile
仅适用于阅读,特别是读取内存映射的I / O寄存器。
它可以用来告诉编译器不要假设一旦它从内存位置读取值不会改变:
while (*p)
{
// ...
}
在上面的代码中,如果没有在循环中写入*p
,编译器可能会决定将读取移到循环之外,更像是这样:
cached_p=*p
while (cached_p)
{
// ...
}
如果p
是指向内存映射I / O端口的指针,那么在每次输入循环之前,您需要第一个检查端口的版本。
如果p
是多线程应用程序中指向内存的指针,则仍然无法保证写入是原子的。
答案 4 :(得分:0)
如果没有锁定,编译器或处理器可能仍然会“重新排序”。并且无法保证对int的写入是原子的。
使用正确的锁定会更好。
答案 5 :(得分:-4)
易失性将解决您的问题,即。它将保证系统所有缓存之间的一致性。然而,它将是低效率的,因为它将为每个R或W访问更新存储器中的变量。只有在需要时,您才可以使用内存屏障进行调整。 如果您正在使用或gcc / icc查看同步内置插件:http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html
编辑(主要是关于pm100评论): 我理解我的信念不是参考,所以我找到了引用的东西:)
设计 volatile关键字是为了防止在某些异步事件出现时可能导致代码不正确的编译器优化。例如, 如果将基元变量声明为volatile,则不允许编译器将其缓存在寄存器中
更有趣:
易失性字段是可线性化的。读取易失性字段就像获取锁定一样;工作存储器无效,并且从存储器重新读取volatile字段的当前值。编写volatile字段就像释放一个锁:volatile字段会立即写回内存。 (这完全是关于一致性,而不是关于原子性)
来自多处理器编程的艺术,Maurice Herlihy& Nir ShavitLock包含内存同步代码,如果你没有锁定,你必须做一些事情并且使用volatile关键字可能是你能做的最简单的事情(即使它是为内存绑定到地址空间的外部设备设计的,它是这不是重点)