Windows + VisualC上是否有易失性读写原子?

时间:2011-08-10 07:42:51

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

此网站上有几个问题询问是否可以使用volatile变量进行原子/多线程访问:例如,请参阅herehereor here

现在,符合C(++)标准的答案显然是没有

然而,在Windows& Visual C ++编译器,情况似乎不太清楚。

我最近answered并引用了volatile

上的official MSDN docs
  

Microsoft特定

     

声明为volatile的对象是(...)

     
      
  • 对volatile对象的写入(volatile write)具有Release语义;   在写入之前发生的全局或静态对象 的引用   指令序列中的volatile对象将在此之前发生   volatile编译二进制文件。
  •   
  • 读取volatile对象(volatile read)具有Acquire语义;对a的引用   全局或静态对象 在读取易失性内存后发生   指令   序列将在编译二进制文件中的volatile读取之后发生。
  •   
     

这允许volatile对象用于多线程应用程序中的内存锁定和释放。

     

[强调我的]

现在,阅读本文,在我看来,MS编译器会处理一个易变量变量,因为std::atomic将在即将推出的C ++ 11标准中。

但是,在comment to my answer中,用户Hans Passant写了“那篇MSDN文章非常不幸,这是错误的。你不能用volatile实现锁定,甚至不能用微软的版本。(...)“


请注意:MSDN中提供的示例看起来非常可疑,因为您通常无法在没有原子交换的情况下实现锁定。 (还有pointed out by Alex。)这个仍然留下了问题。关于本MSDN文章中给出的其他信息的有效性,特别是对于herehere等用例。)


此外,还有Interlocked *函数的文档,特别是InterlockedExchange带有volatile(!?)变量并进行原子读取+写入。 (请注意,我们在SO上的一个问题 - When should InterlockedExchange be used? - 没有权威性地回答是否需要此函数来进行只读或只写原子访问。)

更重要的是,上面引用的volatile文档以某种方式暗示“全局或静态对象”,我认为“真实”acquire/release semantics应适用于所有值。

回到问题

在Windows上,使用Visual C ++(2005 - 2010),将({32}?int?)变量声明为volatile允许对此变量进行原子读取和写入 - 或不是?

对我来说特别重要的是,这应该保存(或不保存)程序运行的处理器或平台的Windows / VC ++ 。 (也就是说,无论是在Itanum2上运行的WinXP / 32bit还是Windows 2008R2 / 64bit都有关系?)

请用可验证的信息,链接,测试用例备份您的答案!

5 个答案:

答案 0 :(得分:6)

是的,它们在windows / vc ++上是原子的(假设你符合对齐要求等或课程)

然而,对于锁定,您需要进行原子测试并设置,或比较和交换instuction或类似物,而不仅仅是原子更新或读取。

否则无法在一个不可分割的操作中测试锁声明它。

编辑:如下所述,无论如何,32位或更低版本的x86上的所有对齐内存访问都是原子的。关键是volatile会使内存访问有序。 (感谢您在评论中指出这一点)

答案 1 :(得分:3)

从Visual C ++ 2005开始,volatile变量是原子的。但这仅适用于此特定类别的编译器和x86 / AMD64平台。例如,PowerPC可能会重新排序内存读/写,并且需要读/写障碍。我不熟悉gcc类编译器的语义,但无论如何使用volatile for atomics都不是很容易实现。

参考,请参阅第一条评论“Microsoft Specific”:http://msdn.microsoft.com/en-us/library/12a04hfd%28VS.80%29.aspx

答案 2 :(得分:1)

有点偏离主题,但不管怎样,让我们​​去吧。

  

...有Interlocked *函数的文档,特别是InterlockedExchange,它接受一个volatile(!)变量......

如果你想到这个:

void foo(int volatile*);

是否说:

  • 参数必须是指向volatile int或
  • 的指针
  • 参数也可以是指向volatile int?
  • 的指针

后者是正确的答案,因为函数可以传递给volatile和非volatile的指针。

因此,InterlockedExchangeX()的参数volatile-qualified这一事实并不意味着它必须只对volatile的整数进行操作。

答案 3 :(得分:1)

重点可能是允许像

这样的东西
singleton& get_instance()
{
    static volatile singleton* instance;
    static mutex instance_mutex;

    if (!instance)
    {
        raii_lock lock(instance_mutex);

        if (!instance) instance = new singleton;
    }

    return *instance;
}

如果在初始化完成之前写入instance,则会中断。使用MSVC语义,可以保证只要看到instance != 0,对象就会被初始化(即使使用传统的volatile语义,也没有正确的屏障语义)。

这种双重检查锁(反)模式实际上很常见,如果你不提供屏障语义就会破坏。但是,如果有保证访问volatile变量是获取+释放障碍,那么它是有效的。

不要依赖volatile的这种自定义语义。我怀疑这是为了不破坏现有的代码库。无论如何,请不要根据MSDN示例写锁。它可能不起作用(我怀疑你只能使用一个障碍写一个锁:你需要原子操作 - CAS,TAS等 - 为此)。

编写双重检查锁模式的唯一可移植方法是使用C ++ 0x ,它提供了合适的内存模型和明确的障碍。

答案 4 :(得分:1)

在x86下,这些操作保证是原子的,无需基于LOCK的指令,如Interlocked*(参见英特尔开发人员手册3A第8.1节):

  

基本内存操作将始终以原子方式执行:

     

•   读或写一个字节

     

•读取或写入对齐的单词   16位边界

     

•读取或写入在32位边界上对齐的双字

     

奔腾处理器(以及更新的处理器)保证   将始终携带以下附加内存操作   原子地说:

     

•读取或写入在64位上对齐的四字   边界

     

•16位访问适合的未缓存内存位置   在32位数据总线中

     

P6系列处理器(以及更新版本   处理器自)保证以下额外的内存   操作将始终以原子方式进行:

     

•未对齐的16-,32-,   和64位访问缓存内存,适合缓存行

这意味着volatile将仅用于防止编译器进行缓存和指令重新排序(MSVC不会为volatile变量发出原子操作,需要明确使用它们)。