我正在阅读这篇文章,我遵循作者的步骤,但得到了不同的结果。
我创建了两个线程。一个是读者,一个是作家。
// 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;
这可能会被重新排序,导致另一个线程出现故障。这种情况不会发生在你的特定环境中是无关紧要的。允许编译器执行此操作,因此代码不正确。