我已经阅读了一段时间,以便了解当使用现代(多核)CPU进行多线程编程时会发生什么。但是,在我阅读this时,我注意到“显式编译器障碍”部分中的代码,该部分不使用volatile来IsPublished
全局。
#define COMPILER_BARRIER() asm volatile("" ::: "memory")
int Value;
int IsPublished = 0;
void sendValue(int x)
{
Value = x;
COMPILER_BARRIER(); // prevent reordering of stores
IsPublished = 1;
}
int tryRecvValue()
{
if (IsPublished)
{
COMPILER_BARRIER(); // prevent reordering of loads
return Value;
}
return -1; // or some other value to mean not yet received
}
问题是,在这里省略IsPublished
的volatile是否安全?很多人都提到“volatile”关键字与多线程编程没什么关系,我同意它们。但是,在编译器优化期间,可以应用“常量折叠/传播”,并且如wiki page所示,如果编译器不了解谁可以更改,则可以将if (IsPublished)
更改为if (false)
IsPublished
的值。我在这里想念或误解了什么吗?
内存障碍可能会阻止编译器的排序和CPU的无序执行,但正如我在previos段中所说,我仍然需要volatile
以避免“持续折叠/传播”这是一个危险的优化特别是在无锁代码中使用全局变量作为标志?
答案 0 :(得分:0)
如果调用tryRecvValue()
一次,则可以安全地省略IsPublished
的 volatile 。如果调用tryRecvValue()
之间存在函数调用(编译器无法证明),它不会更改IsPublished
的 false 值,则情况也是如此。 / p>
// Example 1(Safe)
int v = tryRecvValue();
if(v == -1) exit(1);
// Example 2(Unsafe): tryRecvValue may be inlined and 'IsPublished' may be not re-read between iterations.
int v;
while(true)
{
v = tryRecvValue();
if(v != -1) break;
}
// Example 3(Safe)
int v;
while(true)
{
v = tryRecvValue();
if(v != -1) break;
some_extern_call(); // Possibly can change 'IsPublished'
}
只有当编译器能够证明变量的值时,才能应用常量传播。由于IsPublished
被声明为非常量,因此只有在以下情况下才能证明其值:
在相同程序的主题中读取(再次)变量。
在给定程序的主题中,2到3之间的变量没有改变。
除非你在某种类型的.init函数中调用tryRecvValue()
,否则编译器永远不会在同一个线程中看到 IsPublished 初始化及其读取。因此,根据其初始化来证明此变量的 false 值是不可能的。
根据tryRecvValue
函数中 false (空)分支验证错误 错误的值 在上面的代码中。