我一直在阅读许多来源the volatile
keyword is not helpful in multithreaded scenarios。但是,这个断言经常受到接受volatile
指针的原子操作函数的挑战。
例如,在Mac OS X上,我们有OSAtomic
函数系列:
SInt32 OSIncrementAtomic(volatile SInt32 *address);
SInt32 OSDrecrementAtomic(volatile SInt32 *address);
SInt32 OSAddAtomic(SInt32 amount, volatile SInt32 *address);
// ...
似乎Windows上的volatile
关键字用于Interlocked
操作:
LONG __cdecl InterlockedIncrement(__inout LONG volatile *Addend);
LONG __cdecl InterlockedDecrement(__inout LONG volatile *Addend);
似乎在C ++ 11中,原子类型具有volatile
修饰符的方法,这必然意味着volatile
关键字与原子性有某种关系。
那么,我错过了什么?为什么操作系统供应商和标准库设计者坚持使用volatile
关键字进行线程处理,如果它没用?
答案 0 :(得分:22)
Volatile对多个线程的共享访问没有用处 - 只是它不一定足够:
此外,您还应该注意,示例中API的指针参数上的volatile
限定符实际上只是增加了API接收指向volatile
对象的指针而无需投诉的能力 - 它不要求指针指向实际的volatile
个对象。该标准允许非限定指针自动转换为限定指针。标准中没有提供自动转向(非限定的合格指针)(编译器通常允许它,但发出警告)。
例如,如果InterlockedIncrement()
原型为:
LONG __cdecl InterlockedIncrement(__inout LONG *Addend); // not `volatile*`
仍然可以实现API以在内部正常工作。但是,如果用户有一个他希望传递给API的易失性对象,则需要使用强制转换来防止编译器发出警告。
由于(必要与否),这些API通常与volatile
限定对象一起使用,将volatile
限定符添加到指针参数可防止在使用API时生成无用的诊断,并且会对其造成危害当API与指向非易失性对象的指针一起使用时没有任何内容。
答案 1 :(得分:17)
我突然想到我只是误解了volatile*
的含义。就像const*
意味着指针不应该改变一样,volatile*
意味着指针对象不应该缓存在寄存器中。这是一个可以自由添加的附加约束:尽可能将char*
投射到const char*
,您可以将int*
投射到volatile int*
。
因此,将volatile
修饰符应用于指针只会确保原子函数可用于已volatile
个变量。对于非易失性变量,添加限定符是免费的。我的错误是将原型中关键字的存在解释为使用它的动机,而不是为使用它的人提供便利。
答案 2 :(得分:2)
C ++ 11包含volatile
和非volatile
变量的原子。
如果编译器内在函数指向volatile int
,则表示您可以使用即使该变量是volatile。它不会阻止您在非volatile
数据上使用该功能。
答案 3 :(得分:1)
好吧,关键字'volatile'确保每次变量显示在代码中时,编译器总是从/向内存加载/存储变量的值。
这可以防止某些优化,例如该值只需加载到寄存器中一次,然后多次使用
当您有多个线程可以修改线程之间的“共享”变量时,它很有用。您必须确保始终从/向内存加载/存储值,以便检查其值可以被另一个线程修改的值。如果未使用volatile,则其他线程可能没有将新值写入内存(但将其放入寄存器或其他类型的优化可能已经发生)并且第一个线程不会注意到值的任何更改。
在您的情况下,'volatile SInt32 * address'告诉编译器地址指向的内存是任何来源都要更改的主题。因此需要原子操作。