Visual C ++ x86上的volatile变量和原子操作

时间:2011-02-12 06:29:33

标签: c++ visual-c++ atomic volatile memory-fences

普通加载在x86上获取语义,普通商店具有释放语义,但编译器仍然可以重新排序指令。虽然围栏和锁定指令(锁定的xchg,锁定的cmpxchg)会阻止硬件和编译器重新排序,但仍需要普通的加载和存储来保护编译器障碍。 Visual C ++提供了_ReadWriterBarrier()函数,它可以防止编译器重新排序,同样C ++提供volatile关键字也是出于同样的原因。我写这些信息只是为了确保我把一切都弄好。所以上面写的都是真的,有没有理由将它们标记为将在_ReadWriteBarrier()保护的函数中使用的volatile变量?

例如:

int load(int& var)
{
    _ReadWriteBarrier();
    T value = var;
    _ReadWriteBarrier();
    return value;
}

使变量非易失性是否安全?据我所知,因为函数受到保护,内部编译器无法进行重新排序。另一方面,Visual C ++为volatile变量提供了特殊的行为(不同于标准的变量),它使得volatile可以读取和写入原子载荷和存储,但我的目标是x86,而且x86上的普通加载和存储应该是原子的无论如何,对吧?

提前致谢。

2 个答案:

答案 0 :(得分:2)

也可以在C中使用Volatile。 “volatile”通常用于嵌入式系统,特别是当变量的值可能随时发生变化时 - 代码不采取任何操作 - 三种常见方案包括从内存映射外设寄存器读取或全局变量修改中断服务程序或多线程程序中的程序。

所以这是最后一个可以认为volatile类似于_ReadWriteBarrier的情况。

_ReadWriteBarrier不是函数 - _ReadWriteBarrier不插入任何附加指令,并且它不会阻止CPU重新排列读取和写入 - 它只会阻止编译器重新排列它们。 _ReadWriteBarrier是为了防止编译器重新排序。

MemoryBarrier是为了防止CPU重新排序!

编译器通常会重新排列指令... C ++不包含对多线程程序的内置支持,因此编译器假定在重新排序代码时代码是单线程的。使用MSVC在代码中使用_ReadWriteBarrier,这样编译器就不会在其上移动读写操作。

检查此链接以获取有关这些主题的更详细讨论 http://msdn.microsoft.com/en-us/library/ee418650(v=vs.85).aspx

关于你的代码片段 - 你不必使用ReadWriteBarrier作为SYNC原语 - 不需要第一次调用_ReadWriteBarrier。

使用ReadWriteBarrier时,您不必使用volatile

您写道“它使得易失性读取和写入原子载荷和存储” - 我不认为可以这么说,原子性和波动性是不同的。原子操作被认为是不可分割的 - ...... http://www.yoda.arachsys.com/csharp/threads/volatility.shtml

答案 1 :(得分:1)

注意:我不是这个主题的专家,我的一些陈述“我在互联网上听到的内容”,但我认为我仍然清除了一些误解。

[edit] 一般情况下,我会依赖于x86原子读取和缺少OOOX等特定平台,只能在#ifdef检查的#else保护的隔离的本地优化中使用目标平台,理想情况下伴随着_ReadWriteBarrier路径中的便携式解决方案。

需要注意的事项

  • 读/写操作的原子性
  • 由于编译器优化而重新排序(由于简单的寄存器缓存,这包括另一个线程看到的不同顺序)
  • CPU中的无序执行

可能的误解

1。 据我所知,因为函数受到保护,内部编译器无法重新排序。
[edit] 为了澄清:_ReadWriteBarrier提供了防止指令重新排序的保护,但是,您必须超越函数的范围。在VS 2010中已修复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) 来执行此操作,早期版本可能会被破坏(取决于它们实际执行的优化)。

优化不仅限于功能。有多种机制(自动内联,链接时间代码生成)跨越函数甚至编译单元(并且可以提供比小范围寄存器缓存更重要的优化)。

2. Visual C ++ [...]使得易失性读写原子载荷和存储,
你是在哪里找到那个东西的。 MSDN表示超出标准,会在读写时产生内存障碍,无法保证原子读取。

[edit] 请注意,C#,Java,Delphi等具有不同的内存mdoels,可能会有不同的保证。

3. 无论如何,普通的加载和存储应该是x86上的原子,对吗?
不,他们不是。未对齐的读取不是原子的。如果它们完全一致,它们碰巧是原子 - 除非它被隔离并且容易交换,否则我不会依赖它。否则,你的“x86简化”将成为该目标的锁定。

[edit] 发生未对齐的读取:

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;
}

如果您记得参数必须对齐,并且您控制所有代码,那么这当然是学术性的。我不会再写这样的代码了,因为我经常被过去的懒惰所困扰。

4. 普通加载在x86上获取语义,普通商店具有发布语义
不.X86处理器不使用无序执行(或者更确切地说,没有可见的OOOX - 我认为),但这并不能阻止优化器重新排序指令。

5. _ReadBarrier / _WriteBarrier / _ReadWriteBarrier做所有的魔术 它们不会 - 它们只是阻止优化器重新排序。 MSDN最终为VS2010制作了big bad warning,但信息显然适用于previous versions as well


现在,问你的问题。

我假设代码片段的目的是传递任何变量N,并加载它(原子地?)直接的选择是互锁读取或(在Visual C ++ 2005及更高版本上)易失性读取。

否则在读取之前你需要为编译器和CPU都设置一个屏障,在VC ++中这将是:

_WriteBarrier

Noe {{1}}在MSDN中发出第二个警告: *在以前版本的Visual C ++编译器中,_ReadWriteBarrier和_WriteBarrier函数仅在本地强制执行,不会影响调用树的函数。现在,这些函数一直强制执行调用树。*


希望这是正确的。如果我错了,请纠正我。