如果volatile对于线程无用,为什么原子操作需要指向易失性数据?

zne*_*eak 28 c atomic volatile

我从许多来源读到这个volatile关键字 在多线程场景中没有帮助 .但是,这个断言一直受到接受指针的原子操作函数的挑战.volatile

例如,在Mac OS X上,我们有OSAtomic函数系列:

SInt32 OSIncrementAtomic(volatile SInt32 *address);
SInt32 OSDrecrementAtomic(volatile SInt32 *address);
SInt32 OSAddAtomic(SInt32 amount, volatile SInt32 *address);
// ...
Run Code Online (Sandbox Code Playgroud)

似乎volatileWindows 上的关键字类似于Interlocked操作:

LONG __cdecl InterlockedIncrement(__inout LONG volatile *Addend);
LONG __cdecl InterlockedDecrement(__inout LONG volatile *Addend);
Run Code Online (Sandbox Code Playgroud)

似乎在C++ 11中,原子类型具有volatile修饰符的方法,它必须以某种方式表示volatile关键字与原子性有某种关系.

那么,我错过了什么?为什么操作系统供应商和标准库设计者坚持使用volatile关键字进行线程化,如果它没用?

Mic*_*urr 22

易失性对多线程的共享访问没有用处 - 只是它不一定足够:

  • 它不一定提供可能需要的内存屏障语义;
  • 它不提供原子访问的保证(例如,如果volatile对象大于平台的本机内存字大小)

此外,您还应该注意,volatile示例中API的指针参数上的限定符实际上只是增加了API接收指向volatile对象的指针而无需投诉的能力 - 它不需要指针指向实际volatile对象.该标准允许非限定指针自动转换为限定指针.标准中没有提供自动转向(非限定的合格指针)(编译器通常允许它,但发出警告).

例如,如果InterlockedIncrement()原型为:

LONG __cdecl InterlockedIncrement(__inout LONG *Addend);  // not `volatile*`
Run Code Online (Sandbox Code Playgroud)

仍然可以实现API以在内部正常工作.但是,如果用户有一个他希望传递给API的易失性对象,则需要使用强制转换来防止编译器发出警告.

由于(必要或不必要),这些API通常与volatile限定对象一起使用,将volatile限定符添加到指针参数可防止在使用API​​时生成无用的诊断,并且当API与非指针一起使用时不会造成任何损害.易变物.


zne*_*eak 17

我突然想到我只是误解了它的含义volatile*.非常类似const*意味着指针对象不应该改变,volatile*意味着指针对象不应该缓存在寄存器中.这是一个可以自由添加的附加约束:尽管你可以将a转换char*为a const char*,但你可以将其转换int*为a volatile int*.

因此,将volatile修饰符应用于指针只是确保原子函数可以用于已经volatile变量.对于非易失性变量,添加限定符是免费的.我的错误是将原型中关键字的存在解释为使用它的动机,而不是为使用它的人提供便利.