jas*_* na 6 c++ multithreading volatile cpu-registers c++11
在volatile中:多线程程序员最好的朋友,Andrei Alexandrescu给出了这个例子:
class Gadget
{
public:
void Wait()
{
while (!flag_)
{
Sleep(1000); // sleeps for 1000 milliseconds
}
}
void Wakeup()
{
flag_ = true;
}
...
private:
bool flag_;
};
Run Code Online (Sandbox Code Playgroud)
他说,
... ...编译器得出结论,它可以将flag_缓存在寄存器中...它会损害正确性:在您调用Wait for some Gadget对象之后,虽然另一个线程调用Wakeup,但Wait将永远循环.这是因为flag_的更改不会反映在缓存flag_的寄存器中.
然后他提供了一个解决方案
如果对变量使用volatile修饰符,则编译器不会将该变量缓存在寄存器中 - 每次访问都将访问该变量的实际内存位置.
现在,其他人在stackoverflow和其他地方提到volatile关键字并不真正提供任何线程安全保证,我应该使用std :: atomic或mutex同步,我同意.
但是,以std :: atomic路由为例,内部使用内存栅栏read_acquire和write_release(Acquire和Release Semantics),我看不出它是如何实际修复寄存器缓存问题的.
例如,在x86的情况下,x86/64上的每个加载都意味着获取语义,并且每个存储意味着释放语义,使得x86下的编译代码根本不会发出任何实际的内存屏障.(C++ 11中memory_order_consume的目的)
g = Guard.load(memory_order_acquire);
if (g != 0)
p = Payload;
Run Code Online (Sandbox Code Playgroud)
在Intel x86-64上,Clang编译器为此示例生成紧凑的机器代码 - 每行C++源代码一个机器指令.该系列处理器具有强大的内存模型,因此编译器无需发出特殊的内存屏障指令即可实现读取.
所以....现在假设x86 arch,std :: atomic如何解决注册表问题中的缓存?在编译代码中没有用于读取的内存屏障指令,它似乎与常规读取的编译代码相同.
您是否注意到代码中只有一个寄存器没有负载?来自的显式内存负载_Guard.事实上它确实阻止了寄存器中的缓存.
现在它如何做到这取决于特定平台的实现std::atomic,但它必须这样做.
顺便说一句,亚历山大夫斯库的推理对于现代平台来说是完全错误的.虽然确实可以volatile防止编译器在寄存器中进行高速缓存,但它并不能阻止CPU或硬件进行类似的高速缓存.在某些平台上,它可能恰好是足够的,但是当完全可移植的替代方案随时可用时,绝对没有理由编写可能会破坏未来CPU,编译器,库或平台的无法使用的非可移植代码.