注意:我不是这个话题的专家,我的一些陈述是 “我在互联网上听到的”,但我认为我仍然可以澄清一些误解。
[编辑]一般来说,我会依赖平台特定的东西,例如 x86 原子读取和缺乏 OOOX,仅在隔离的本地优化中,这些优化由#ifdef
检查目标平台保护,理想情况下伴随着#else
路径中的可移植解决方案。
需要注意的事项
- 读/写操作的原子性
- 由于编译器优化而重新排序(这包括由于简单的寄存器缓存而由另一个线程看到的不同顺序)
- CPU中的乱序执行
可能的误解
1. 据我了解,因为函数受到保护,内部编译器无法进行重新排序。
[编辑]澄清一下:_ReadWriteBarrier
提供了防止指令重新排序的保护,但是,您必须超越函数的范围。_ReadWriteBarrier
已在 VS 2010 中修复以执行此操作,早期版本可能会被破坏(取决于它们实际执行的优化)。
优化不仅限于功能。有多种机制(自动内联、链接时间代码生成)跨越函数甚至编译单元(并且可以提供比小范围寄存器缓存更重要的优化)。
2. Visual C++ [...] 使 volatile 读写原子加载和存储,
你在哪里找到的?MSDN表示,超出标准,会在读写周围设置内存屏障,不能保证原子读取。
[编辑]请注意,C#、Java、Delphi 等具有不同的内存 mdoels,可能会做出不同的保证。
3. 普通加载和存储在 x86 上应该是原子的,对吧?
不,他们不是。未对齐的读取不是原子的。如果它们对齐良好,它们恰好是原子的——除非它被隔离且易于交换,否则我不会依赖这一事实。否则,您的“x86 简化”将成为对该目标的锁定。
[编辑]未对齐的读取发生:
char * c = new char[sizeof(int)+1];
load(*(int *)c); // allowed by standard to be unaligned
load(*(int *)(c+1)); // unaligned with most allocators
#pragma pack(push,1)
struct
{
char c;
int i;
} foo;
load(foo.i); // caller said so
#pragma pack(pop)
如果您记得参数必须对齐并且您控制所有代码,那么这当然都是学术性的。我不会再写这样的代码了,因为我经常被过去的懒惰所困扰。
4. 普通加载在 x86 上具有获取语义,普通存储具有释放语义
否。x86 处理器不使用乱序执行(或者更确切地说,没有可见的 OOOX - 我认为),但这并不能阻止优化器重新排序指示。
5. _ReadBarrier / _WriteBarrier / _ReadWriteBarrier 做了他们没有做的所有魔法 - 他们只是阻止优化器重新排序。MSDN 终于对 VS2010 提出了一个严重的错误警告,但该信息显然也适用于以前的版本。
现在,回答你的问题。
我假设该片段的目的是传递任何变量 N,并加载它(原子地?)直接的选择是互锁读取或(在 Visual C++ 2005 及更高版本上)易失性读取。
否则,在读取之前,您需要为编译器和 CPU 设置屏障,在 VC++ 客厅中,这将是:
int load(int& var)
{
// force Optimizer to complete all memory writes:
// (Note that this had issues before VC++ 2010)
_WriteBarrier();
// force CPU to settle all pending read/writes, and not to start new ones:
MemoryBarrier();
// now, read.
int value = var;
return value;
}
Noe_WriteBarrier
在 MSDN 中有第二个警告: *在 Visual C++ 编译器的过去版本中,_ReadWriteBarrier 和 _WriteBarrier 函数仅在本地强制执行,不会影响调用树上的函数。这些函数现在在调用树中一直强制执行。*
我希望这是正确的。stackoverflowers,如果我错了,请纠正我。