我有一个多线程C ++应用程序。
现在我知道对于全局共享变量,在某些情况下应该在检查变量状态时使用volatile,否则编译器可能会认为变量的值永远不会改变(在该线程中)。
但是,如果不是检查变量的状态而是调用返回变量值的方法,该怎么办?例如:
static int num = 0;
...
void foo()
{
while(getNum() == 0)
{
// do something (or nothing)
}
}
我还需要将 num 变为易变变量吗?或者编译器是否认识到,因为我正在使用一种方法来访问该变量num,所以它不会缓存结果?
有人有任何想法吗?
提前致谢,
〜儒略
编辑:在我的while循环内部,我删除了睡眠调用并将其替换为通用内容,例如注释以执行某些操作(或者什么都没有)
答案 0 :(得分:4)
不,只要您正在进行必要的同步,就永远不需要volatile
。
调用线程库同步函数,无论它们在您的平台上是什么,都应该注意使本地“缓存”值无效并使编译器重新加载全局变量。
在这种特殊情况下,sleep
可能会产生这样的效果,但无论如何它都不是一个好的实现。 num
上应该有一个条件变量,用setter函数保护它,并让setter函数向foo
发送信号。
关于具体问题,该功能是否隐藏了优化访问权限,这取决于实现和情境。最好的办法是在编译器的单独调用中编译getter函数,但即使这样,也无法保证不会发生过程间优化。例如,某些平台可能会将IR代码放入.o
文件中,并在“链接器”阶段执行代码生成。
上述关键词:1。只要您进行必要的同步和2. 可能会产生这种影响。
1:sleep
或空忙循环不是“必要的同步”。这不是编写多线程程序的正确方法。因此,在这种情况下可能需要挥发性。
2:是的,sleep
可能不会被执行I / O函数计算,甚至可能被标记为纯粹且没有副作用。在这种情况下,全局volatile
是必要的。但是,我怀疑是否真的已经分发了哪些实现会破坏sleep
这样的循环,因为它们很不常见。
答案 1 :(得分:2)
你的建议基本上是对“volatile”的误用,它的真正目的是告诉编译器变量可能被外部硬件或系统中的其他进程修改,因此,每次都需要真正从内存中读取它的用途。
它不会保护您免受程序中的线程冲突等的影响,但在您的情况下,您似乎正在使用该标志来发出关闭请求的信号。
如果你知道只有一个控制线程会更新变量,它实际上可以在没有同步的情况下执行此操作。此外,我会使用一点操作来设置标志,因为这更可能是更多硬件上的“原子”。
num && x'00000001'
答案 2 :(得分:0)
不幸的是,挥发性语义有点愚蠢。 volatile的概念并不真正意味着用于线程化。
Potatoswatter是正确的,调用OS同步原语通常会阻止优化编译器从循环中提取num的读取。但它适用于sorta与使用访问器方法工作的相同原因......偶然。
编译器看到您调用的函数不能立即用于内联或分析,因此必须假设某个其他函数可以使用的任何变量都可能在该opaque函数中被读取或更改。因此,在进行调用之前,编译器需要将所有这些“全局”变量写回内存。
在核心,我们为jinx.h添加了一个内联函数,它以更直接的方式实现了这一点。如下所示:
inline void memory_barrier() { asm volatile("nop" ::: "memory"); }
这是相当微妙的,但它有效地告诉编译器(gcc)它无法摆脱这个不透明的asm块,并且opaque asm可以读取或写入任何全局可见的内存块。这有效地阻止了编译器跨越此边界重新排序加载/存储。
对于你的例子:
memory_barrier(); while(num == 0){ memory_barrier(); ... }
现在,num的读取卡住了。而且更重要的是,它与其他代码相关联。所以你可以:
while (flag == 0) { memory_barrier(); } // spin
process data[0..N]
另一个主题是:
populate data[0..N]
memory_barrier();
flag = 1;
PS。如果你做这种类型的事情(基本上创建自己的同步原语),性能胜利可能很大,但质量风险很高。 Jinx特别擅长在这些无锁结构中发现错误。因此,您可能希望使用它或其他工具来帮助测试这些内容。
PPS。 linux社区有一篇关于这个名为“volatile被认为有害”的帖子,请查看。
答案 3 :(得分:-1)
从技术上讲,它确实需要标记为volatile。编译器可以自由地执行他们想要的操作,以便优化代码,只要它继续符合c ++抽象机器规范即可。具有足够资源的符合标准的编译器可以内联getNum的所有实例,将num的值移动到寄存器中(或简单地,注意到它从未实际改变过任何代码,将其视为常量),用于程序的整个生命周期
实际上,没有(当前)CPU有足够的自由寄存器,即使最激进的优化编译器也会选择这样做。但如果需要正确性 - 需要挥发性。