多线程和运算符++关于原始类型
所以我读了很多关于共享变量、多线程和易失性主题的文章,无论是这里还是其他地方。
如果您考虑以下代码:
class C {
int x;
public:
C() : x(0) { }
void Operation() {
AcquireMutex();
++x;
ReleaseMutex();
}
};
现在,如果我已经理解了到目前为止所读到的所有内容,那么这将是更新 x
的正确方法,对吗?正确的编译器不会对代码重新排序,以在调用 AcquireMutex()
之前缓存 x
的值,对吗?
我一直有用易失性
标记此类变量的习惯。很久以前,当恐龙在陆地上漫步时,我在学校学到了一些东西,但从未真正反思过它。在阅读了有关该主题的文章后,我似乎浪费了生命中的几分钟时间来输入(对于这些类型的用途)无用的关键字...
更新: 好的,如果我将 Operation()
更改为:
void Operation() {
AcquireMutex();
++x;
ReleaseMutex();
AcquireMutex();
++x;
ReleaseMutex();
}
现在,让我们忽略互斥体的使用以及 InterlockedIncrement() 等内在函数或其他内容。这有点超出了我的观点。
如果x
没有标记为易失性
,上面的代码是线程安全的吗?编译器是否会决定在第一次增量后将 x 的最后一个值保存在寄存器中,然后仅增量寄存器的值,并在最后一次增量时将其存储在内存中?如果是这种情况,那么上面的代码就不是线程安全的。什么给?编译器是否会假设在调用任何函数后,所有缓存的变量都被视为“脏”,从而强制编译器发出读取操作?
So I've read a bunch of articles, both here on SO, and elsewhere, on the topic of shared variables, multiple threads and volatile.
If you consider the following code:
class C {
int x;
public:
C() : x(0) { }
void Operation() {
AcquireMutex();
++x;
ReleaseMutex();
}
};
Now, if I have understood everything I've read so far, this would be a correct way to update x
, right? A correct compiler will not reorder the code, to cache the value of x
before the call to AcquireMutex()
, right?
I've always had a habit of tagging such variables with volatile
. Something I picked up in school way back when dinosaurs roamed the lands, and never really reflected on it. After reading articles on the topic, it would seem that I have wasted a few minutes of my life typing out a (for these types of uses) useless keyword...
UPDATE:
Ok, so if I change Operation()
to this instead:
void Operation() {
AcquireMutex();
++x;
ReleaseMutex();
AcquireMutex();
++x;
ReleaseMutex();
}
Now, let's disregard the use of mutexes, and intrinsics such as InterlockedIncrement(), or whatever. It is kind of besides my point.
If x
is not marked as volatile
, will the code above be thread safe? Could it be that a compiler decides to hold the last value of x
in a register after the first increment, and then just increment the register's value, and store that in memory at the last increment? If that is the case, then the code above is not thread safe. What gives? Will the compiler assume that after a call to any function, all cached variables are considered "dirty", thus forcing the compiler to issue read operations?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
易失性
没有提及原子性。其目的是防止缓存不应缓存的内存位置(例如,硬件设备 DMA 端口。)(编辑:此措辞是指生成的代码的“缓存”。例如,非易失性变量可以从内存中读取,然后无限期地保存在寄存器中,Arkadiy 在下面的注释中提供了更精确的定义。)正如其他人所指出的,C 或 C++ 中没有任何操作。保证是原子的。您需要根据需要自行管理互斥体或其他守卫。
volatile
says nothing about atomicity. Its purpose is to prevent caching of memory locations that should not be cached (e.g., hardware device DMA ports.) (EDIT: This wording was in reference to "caching" by the generated code. For example, a non-volatile
variable might be read from memory, then kept in a register indefinitely. Arkadiy offered a more precise definition in a comment, below.)And as others have noted, no operation in C or C++ is guaranteed to be atomic. You're on your own to manage mutexes or other guards as needed.
我不太确定马丁是对的。看看这个:
InterlockedIncrement 函数
如果32 位递增是原子的,为什么需要 InterlockedIncremenet?
话虽这么说,你永远不应该对这种东西使用互斥锁,这是一种巨大的浪费。使用 CPU 内在函数,如 win32 api 中的 Interlocked* 函数(以及其他编译器库中的等效函数)。
I am not so sure Martin is right. Look at this:
InterlockedIncrement Function
If 32 bit incrementing was atomic, why the need for
InterlockedIncremenet
?This being said, you should never use a mutex for this kind of stuff, it's a huge waste. Use the CPU intrinsics like the
Interlocked*
functions in the win32 api (and their equivalents in other compiler libraries).如果您在 Windows 上使用 Visual Studio,您可以尝试使用所谓的内在函数:
有关编译器内在函数的更多信息。
不知道其他操作系统,但我确信也有内在函数。
If you're on Windows, using Visual Studio, you could try with so-called intrinsics:
More on compiler intrinsics.
Don't know about other OS, but I'm sure there are also intrinsics.