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

普通加载在x86上获得语义,普通存储具有释放语义,但编译器仍然可以重新排序指令。 虽然防护和锁定指令(锁定xchg,锁定cmpxchg)可防止硬件和编译器重新排序,但使用编译器屏障仍然需要简单的加载和存储。 Visual C ++提供了_ReadWriterBarrier()函数,它可以防止编译器重新排序,同样C ++也提供volatile关键字。 我写下所有这些信息只是为了确保我把所有事情都做好。 因此,上面所写的所有内容都是真实的,是否有任何理由标记为将在用_ReadWriteBarrier()保护的函数中使用的volatile变量?

例如:

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

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

提前致谢。


C中也有可用的关键字。 嵌入式系统中经常使用“volatile”,特别是当变量的值随时可能改变而不需要代码采取任何动作时 - 三种常见的情况包括从存储器映射的外设寄存器读取数据或全局变量中断服务例程或多线程程序中的中断服务例程。

所以这是挥发性可以被认为与_ReadWriteBarrier类似的最后一种情况。

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

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

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

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

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

使用ReadWriteBarrier时,您不必使用volatile

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


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

[编辑]一般情况下,我会依靠平台的细节,例如86原子读取和缺乏OOOX只由一个守卫个别的,局部的优化#ifdef检查目标平台,最好是伴随着一个便携式解决方案#else路径。

需要注意的事情

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

    1.据我所知,因为函数是受保护的,编译器内部不需要重新排序。
    [编辑]为了澄清:该_ReadWriteBarrier提供保护,防止指令重新排序,但是,你必须超越功能的范围。 _ReadWriteBarrier已经在VS 2010中修复过,早期的版本可能会被破坏(取决于他们实际做的优化)。

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

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

    [编辑]需要注意的是C#,Java和德尔福等有不同的内存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 that _WriteBarrier在MSDN中有第二个警告:*在以前版本的Visual C ++编译器中,_ReadWriteBarrier和_WriteBarrier函数只在本地执行,并不影响调用树上的函数。 这些函数现在在调用树中一直执行。*


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

    链接地址: http://www.djcxy.com/p/26939.html

    上一篇: volatile variable and atomic operations on Visual C++ x86

    下一篇: Why is std::function not equality comparable?