在这种情况下需要记忆障碍还是只是一种波动



我正在阅读这篇文章,我遵循作者的步骤,但得到了不同的结果。

我创建了两个线程。一个是读者,一个是作家。

// volatile uint64_t variable1 = 0; <- global
// uint64_t* variable2_p = new uint64_t(0); <- in main function
// const unsigned ITERATIONS = 2000000000; <- global
void *reader(void *variable2) {
    volatile uint64_t *variable2_p = (uint64_t *)variable2;
    // bind this thread to CPU0
    unsigned i, failureCount = 0;
    for (i=0; i < ITERATIONS; i++) {
            uint64_t v2 = *variable2_p;
            uint64_t v1 = variable1;
            if (v2 > v1) {
                failureCount++;
                printf("v1:%" PRIu64 ", v2:%" PRIu64 "n", v1, v2);
            }
    }
    printf("%u failure(s)", failureCount);
    return NULL;
}
void *writer(void *variable2) {
    volatile uint64_t *variable2_p = (uint64_t *)variable2;
    // bind this thread to CPU1
    for (;;) {
        variable1 = variable1 + 1;
        *variable2_p = (*variable2_p) + 1;
    }
    return NULL;
}

在上面的文章中,作者说比较v2 <= v1可能会失败一段时间,因为编译器或处理器可能会改变执行顺序。

但我试了很多次,没有任何失败的案例。我很困惑的是,只有volatile才有权使用这种情况吗?或者它会导致一些微妙的错误?

如果不好,请给我举个例子。非常感谢。

compile command: g++  -O2 -Wall -g -o foo foo.cc -lpthread
uname -a: Linux Wichmann 3.5.0-48-generic #72~precise1-Ubuntu SMP Tue Mar 11 20:09:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
cpuid: Intel(R) Core(TM) i5-3230M CPU @ 2.60GHz

在所有机器上,它可能失败并不意味着它会失败。如果例如,你在一个核心上运行,它可能不会失败如果你在多核Alpha上运行,它几乎肯定会在某些时候失败。在其他机器上结果会有所不同,具体取决于各种因素。

至于volatile,它不能保证多线程密码如果您也使用内联,则可能是必要的汇编程序或其他编译器无法理解的东西,但是否则:任何时候你都要做其他必要的事情来确保线程安全,您不需要volatile。特别是,如果使用C++11原子类型或线程原语volatile从来没有必要。

编辑

实际上,在这种非常特殊的情况下,代码是正确的,因为variable1都是volatile,并且指针variable2_p被标记为指向volatile的指针。这强制执行内存访问的顺序。

易失性经常被滥用,所以我在这里跳枪了,对不起。

旧答案:

使用volatile只能保证两件事:

  • 读取和写入发生在变量所在的实际内存地址,并且没有优化或保留在寄存器中
  • volatile变量的顺序访问不会重新排序

你问了一个例子,但你自己在问题中给出了:

variable1 = variable1 + 1;
*variable2_p = (*variable2_p) + 1;

这可能会被重新排序,导致另一个线程出现故障。这种情况不会发生在你的特定环境中是无关紧要的。允许编译器执行此操作,因此代码不正确。

最新更新