0

我已经阅读了一段时间,以便更好地了解使用现代(多核)CPU 进行多线程编程时发生的情况。但是,当我阅读本文时,我注意到“显式编译器障碍”部分中的以下代码,它没有将 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 页面所示,如果编译器不太了解谁可以if (IsPublished)更改. 我在这里错过或误解了什么吗?if (false)IsPublished

内存屏障可以防止 CPU 的编译器排序和乱序执行,但正如我在上一段中所说,我仍然需要volatile避免“恒定折叠/传播”,这是一种危险的优化,尤其是使用全局变量作为标志无锁码?

4

1 回答 1

0

如果tryRecvValue()被调用一次,省略volatile for是安全的IsPublished。如果在调用之间存在一个函数调用,编译器tryRecvValue()无法证明它不会改变.IsPublished

// 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被声明为非常量,所以只有在以下情况下才能证明其值:

  1. 变量被分配给给定值从变量中读取后跟分支,仅在变量具有给定值的情况下执行。
  2. 在同一程序的线程中(再次)读取变量。

  3. 在给定程序的线程中,2 到 3 之间的变量不会改变。

除非您调用tryRecvValue()某种 .init 函数,否则编译器将永远不会在同一个线程中看到IsPublished初始化及其读取。因此,不可能根据其初始化来证明此变量的错误值。

根据函数中的(空)分支证明IsPublished的值是可能的,见上面的代码。tryRecvValueExample 2

于 2015-04-30T08:17:17.077 回答