这里有2个基本不相关的项目,总是混淆。
volatile 用于告诉 编译器 生成代码以从内存中读取变量,而不是从寄存器中读取。并且不要重新排序代码。一般来说,不要优化或走“捷径”。
内存屏障(由互斥体、锁等提供),正如 Herb Sutter 在另一个答案中所引用的那样,用于防止 CPU 重新排序读/写内存请求,无论编译器如何表示去做吧。即不要优化,不要走捷径 - 在 CPU 级别。
相似,但实际上非常不同。
在您的情况下,并且在大多数锁定情况下,不需要 volatile 的原因是因为为了锁定而进行了 函数调用。即:
影响优化的正常函数调用:
external void library_func(); // from some external library
global int x;
int f()
{
x = 2;
library_func();
return x; // x is reloaded because it may have changed
}
除非编译器可以检查 library_func() 并确定它没有触及 x,否则它将在返回时重新读取 x。这甚至没有 volatile。
线程:
int f(SomeObject & obj)
{
int temp1;
int temp2;
int temp3;
int temp1 = obj.x;
lock(obj.mutex); // really should use RAII
temp2 = obj.x;
temp3 = obj.x;
unlock(obj.mutex);
return temp;
}
在为 temp1 读取 obj.x 后,编译器将重新读取 temp2 的 obj.x - 不是因为锁的魔力 - 而是因为不确定 lock() 是否修改了 obj。您可能可以设置编译器标志来积极优化(无别名等),因此不会重新读取 x,但是您的一堆代码可能会开始失败。
对于 temp3,编译器(希望)不会重新读取 obj.x。
如果由于某种原因 obj.x 可能在 temp2 和 temp3 之间发生变化,那么您将使用 volatile(并且您的锁定将被破坏/无用)。
最后,如果你的 lock()/unlock() 函数被内联了,也许编译器可以评估代码并看到 obj.x 没有改变。但我在这里保证两件事之一:
- 内联代码最终会调用一些操作系统级别的锁定函数(从而阻止评估)或
- 您调用一些编译器将识别的 asm 内存屏障指令(即包装在像 __InterlockedCompareExchange 这样的内联函数中),从而避免重新排序。
编辑:附注我忘了提到 - 对于 pthreads 的东西,一些编译器被标记为“POSIX compliant”,这意味着它们将识别 pthread_ 函数并且不会围绕它们进行糟糕的优化。即,即使 C++ 标准还没有提到线程,那些编译器会(至少是最低限度地)。
所以,简短的回答
你不需要 volatile。