该网站上有几个问题询问是否可以使用 易失性
变量进行原子/多线程访问:请参阅此处,此处,或此处。
现在,符合 C(++) 标准的答案显然是否。
但是,在 Windows 和Visual C++编译器,情况似乎不太清楚。
我最近回答了并引用了< a href="http://msdn.microsoft.com/en-us/library/12a04hfd%28v=VS.100%29.aspx" rel="nofollow noreferrer">关于 易失性
的官方 MSDN 文档
微软特定
声明为易失性的对象是 (...)
- 对易失性对象的写入(易失性写入)具有释放语义;
对全局或静态对象?的引用发生在写入之前
指令序列中的易失性对象将在此之前发生
易失性写入已编译的二进制文件中。
- 对易失性对象的读取(易失性读)具有 Acquire 语义;对a的引用
全局或静态对象?,在读取易失性内存后发生
操作说明
序列将在编译的二进制文件中的易失性读取之后发生。
这允许将易失性对象用于多线程应用程序中的内存锁定和释放。
[强调我的]
现在,阅读本文,在我看来,易失性变量将是MS 编译器将在即将推出的 C++11 标准中将其视为 std::atomic
。
但是,在评论我的答案 ,用户 Hans Passant 写道“那篇 MSDN 文章是非常不幸,这是完全错误的。即使使用 Microsoft 的版本,也无法实现锁定(...)”
请注意:MSDN 中给出的示例。看起来很可疑,因为如果没有原子交换,通常无法实现锁。 (也正如 Alex 所指出的那样。 )这仍然留下了问题。这篇 MSDN 文章中给出的其他信息的有效性,特别是对于诸如 此处。)
此外,还有以下文档Interlocked* 功能,尤其是 InterlockedExchange
with 需要一个 易失性(!?) 变量并执行原子操作读+写。 (请注意,我们关于 SO 的一个问题 - 何时应该使用 InterlockedExchange? - 并没有权威地回答此功能是否有效只读或只写原子访问需要。)
更重要的是,上面引用的易失性
文档以某种方式暗示了“全局或静态对象”,我本以为“真实”<一个href="http://blogs.msdn.com/b/oldnewthing/archive/2008/10/03/8969397.aspx" rel="nofollow noreferrer">获取/释放语义 应适用于所有值。
回到问题
在 Windows 上,使用 Visual C++(2005 - 2010),将(32 位? int?)变量声明为 易失性
是否允许原子读取和写入该变量?
对我来说特别重要的是,这应该在 Windows/VC++ 上保持(或不保持)独立程序运行的处理器或平台。 (也就是说,运行在 Itanum2 上的 WinXP/32 位还是 Windows 2008R2/64 位有关系吗?)
请用可验证的信息、链接、测试用例来支持您的答案!
There are a couple of questions on this site asking whether using a volatile
variable for atomic / multithreaded access is possible: See here, here, or here for example.
Now, the C(++) standard conformant answer is obviously no.
However, on Windows & Visual C++ compiler, the situation seems not so clear.
I have recently answered and cited the official MSDN docs on volatile
Microsoft Specific
Objects declared as volatile are (...)
- A write to a volatile object (volatile write) has Release semantics;
a reference to a global or static object? that occurs before a write to
a volatile object in the instruction sequence will occur before that
volatile write in the compiled binary.
- A read of a volatile object (volatile read) has Acquire semantics; a reference to a
global or static object? that occurs after a read of volatile memory in the
instruction
sequence will occur after that volatile read in the compiled binary.
This allows volatile objects to be used for memory locks and releases in multithreaded applications.
[emphasis mine]
Now, reading this, it would appear to me that a volatile variable will be treated by the MS compiler as std::atomic
would be in the upcoming C++11 standard.
However, in a comment to my answer, user Hans Passant wrote "That MSDN article is very unfortunate, it is dead wrong. You can't implement a lock with volatile, not even with Microsoft's version. (...)"
Please note: The example given in the MSDN seems pretty fishy, as you cannot generally implement a lock without atomic exchange. (As also pointed out by Alex.) This still leaves the question wrt. to the validity of the other infos given in this MSDN article, especially for use cases like here and here.)
Additionally, there are the docs for The Interlocked* functions, especially InterlockedExchange
with takes a volatile(!?) variable and does an atomic read+write. (Note that one question we have on SO -- When should InterlockedExchange be used? -- does not authoritatively answer whether this function is needed for a read-only or write-only atomic access.)
What's more, the volatile
docs quoted above somehow allude to "global or static object", where I would have thought that "real" acquire/release semantics should apply to all values.
Back to the question
On Windows, with Visual C++ (2005 - 2010), will declaring a (32bit? int?) variable as volatile
allow for atomic reads and writes to this variable -- or not?
What is especially important to me is that this should hold (or not) on Windows/VC++ independently of the processor or platform the program runs on. (That is, does it matter whether it's a WinXP/32bit or a Windows 2008R2/64bit running on Itanum2?)
Please back up your answer with verifiable information, links, test-cases!
发布评论
评论(5)
是的,它们在 windows/vc++ 上是原子的(假设您满足对齐要求等或课程)
但是对于锁,您需要原子测试和设置,或比较和交换指令或类似指令,而不仅仅是原子更新或读取。
否则,无法在一个不可分割的操作中测试锁并声明它。
编辑:如下所述,32 位或更低的 x86 上的所有对齐内存访问无论如何都是原子的。关键点是,易失性使内存访问有序。 (感谢您在评论中指出这一点)
Yes they are atomic on windows/vc++ (Assuming you meet alignment requirements etc or course)
However for a lock you would need an atomic test and set, or compare and exchange instuction or similar, not just an atomic update or read.
Otherwise there is no way to test the lock and claim it in one indivisable operation.
EDIT: As commented below, all aligned memory accesses on x86 of 32bit or below are atomic anyway. The key point is that volatile makes the memory accesses ordered. (Thanks for pointing this out in the comments)
从 Visual C++ 2005 开始,易失性变量是原子的。但这仅适用于此类特定的编译器和 x86/AMD64 平台。例如,PowerPC 可能会重新排序内存读/写,并且需要读/写屏障。我不熟悉 gcc 类编译器的语义,但无论如何,使用 volatile 进行原子操作都不太可移植。
参考,请参阅第一个注释“Microsoft Specific”:http:// /msdn.microsoft.com/en-us/library/12a04hfd%28VS.80%29.aspx
As of Visual C++ 2005 volatile variables are atomic. But this only applies to this specific class of compilers and to x86/AMD64 platforms. PowerPC for example may reorder memory reads/writes and would require read/write barriers. I'm not familar what the semantics are for gcc-class compilers, but in any case using volatile for atomics is not very portable.
reference, see first remark "Microsoft Specific": http://msdn.microsoft.com/en-us/library/12a04hfd%28VS.80%29.aspx
在 x86 下,这些操作保证是原子的,不需要基于 LOCK 的指令,例如 Interlocked*(请参阅英特尔开发人员手册 3A 第 8.1 节):
这意味着 易失性 只会用于防止编译器进行缓存和指令重新排序(MSVC 不会为易失性变量发出原子操作,它们需要明确使用)。
under x86, these operations are guaranteed to be atomic without the need for LOCK based instructions such as
Interlocked*
(see intel's developer manuals 3A section 8.1):This means
volatile
will only every serve to prevent caching and instruction reordering by the compiler (MSVC won't emit atomic operations for volatile variables, they need to be explicitly used).有点偏离主题,但我们还是来吧。
如果您考虑一下:
它是否说:
后者是正确的答案,因为该函数可以传递指向易失性和非易失性 int 的指针。
因此,InterlockedExchangeX() 的参数具有 volatile 限定这一事实并不意味着它必须仅对易失性整数进行操作。
A bit off-topic, but let's have a go anyway.
If you think about this:
Does it say:
The latter is the correct answer, since the function can be passed both pointers to volatile and non-volatile int's.
Hence, the fact that
InterlockedExchangeX()
has its argument volatile-qualified does not imply that it must operate on volatile integers only.重点可能是允许
在初始化完成之前写入实例时会中断的内容。使用 MSVC 语义,您可以保证一旦看到
instance != 0
,该对象就已完成初始化(如果没有适当的屏障语义,即使使用传统的易失性语义,情况也不会如此)。这种双重检查锁(反)模式实际上很常见,如果不提供屏障语义,就会被破坏。但是,如果保证对 volatile 变量的访问是获取 + 释放屏障,那么它就可以工作。
不过,不要依赖
易失性
的此类自定义语义。我怀疑引入这一点并不是为了破坏现有的代码库。无论如何,不要按照MSDN的例子来写锁。它可能不起作用(我怀疑你是否可以只使用屏障来编写锁:你需要原子操作——CAS、TAS 等——为此)。编写双重检查锁定模式的唯一可移植方法是使用 C++0x,它提供了合适的内存模型和显式屏障。
The point is probably to allow stuff like
which would break if
instance
was written to before initialization was complete. With MSVC semantics, you are guaranteed that as soon as you seeinstance != 0
, the object has finished being initialized (which is not the case without proper barrier semantics, even with traditional volatile semantics).This double-checked lock (anti-)pattern is quite common actually, and broken if you don't provide barrier semantics. However, if there are guarantees that accesses to
volatile
variables are acquire + release barriers, then it works.Don't rely on such custom semantics of
volatile
though. I suspect this has been introduced not to break existing codebases. In any way, don't write locks according to MSDN example. It probably doesn't work (I doubt you can write a lock using just a barrier: you need atomic operations -- CAS, TAS, etc -- for that).The only portable way to write the double-checked lock pattern is to use C++0x, which provides a suitable memory model, and explicit barriers.