此网站上有几个问题询问是否可以使用volatile
变量进行原子/多线程访问:例如,请参阅here,here,or here
现在,符合C(++)标准的答案显然是没有。
然而,在Windows& Visual C ++编译器,情况似乎不太清楚。
我最近answered并引用了volatile
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文章中给出的其他信息的有效性,特别是对于here和here等用例。)
此外,还有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都有关系?)
请用可验证的信息,链接,测试用例备份您的答案!
答案 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和非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变量发出原子操作,需要明确使用它们)。