所以我在SO和其他地方阅读了很多文章,主题是共享变量,多线程和volatile。
如果您考虑以下代码:
class C {
int x;
public:
C() : x(0) { }
void Operation() {
AcquireMutex();
++x;
ReleaseMutex();
}
};
现在,如果我已经理解了到目前为止所读到的所有内容,这将是更新x
的正确方法,对吧?正确的编译器不会重新排序代码,在调用x
之前缓存AcquireMutex()
的值,对吗?
我一直习惯用volatile
标记这些变量。当恐龙在陆地上漫游时,我在学校里回来的东西,从未真正反映过它。在阅读有关该主题的文章之后,似乎我浪费了几分钟的时间来输入(对于这些类型的用途)无用的关键字......
更新
好的,所以如果我改为Operation()
而不是:
void Operation() {
AcquireMutex();
++x;
ReleaseMutex();
AcquireMutex();
++x;
ReleaseMutex();
}
现在,让我们忽略互斥体的使用,以及诸如InterlockedIncrement()等内在函数。这是我的观点。
如果x
不标记为volatile
,上述代码是否可以线程安全?可能是编译器决定在第一个增量之后在寄存器中保存x
的最后一个值,然后只是增加寄存器的值,并将其存储在内存中的最后一个增量?如果是这种情况,那么上面的代码不是线程安全的。是什么赋予了?编译器是否会假设在调用任何函数之后,所有缓存的变量都被认为是“脏”的,从而迫使编译器发出读操作?
答案 0 :(得分:2)
我不太确定马丁是对的。看看这个:
如果32位递增是原子的,为什么需要InterlockedIncremenet
?
话虽这么说,你永远不应该使用互斥体这种东西,这是一个巨大的浪费。使用CPU内在函数,如win32 api中的Interlocked*
函数(以及它们在其他编译器库中的等价函数)。
答案 1 :(得分:2)
volatile
对原子性一无所知。其目的是防止缓存不应缓存的内存位置(例如,硬件设备DMA端口)。(编辑:此措辞是由生成的代码引用“缓存”。例如,可以从内存中读取非volatile
变量,然后无限期地保存在寄存器中.Arkadiy在下面的注释中提供了更精确的定义。)
正如其他人所指出的那样,C或C ++中的任何操作都不能保证是原子的。您可以根据需要自行管理互斥锁或其他防护。
答案 2 :(得分:1)
如果你在Windows上使用Visual Studio,你可以尝试使用所谓的内在函数:
#include <intrin.h>
class C {
int x;
public:
C() : x(0) { }
void Operation() {
_InterlockedIncrement(&x);
}
};
More on compiler intrinsics. 不知道其他操作系统,但我确信也有内在的。