什么时候写/读会影响主存?
当我将值写入字段时,关于新值何时保存在主内存中,我得到什么保证?例如,我如何知道处理器没有将新值保留在其私有缓存中,而是更新了主内存?
另一个例子:
int m_foo;
void Read() // executed by thread X (on processor #0)
{
Console.Write(m_foo);
}
void Write() // executed by thread Y (on processor #1)
{
m_foo = 1;
}
是否有可能在 Write() 执行完成后,其他线程执行 Read() 但实际上会看到“0”作为当前值? (因为也许之前对 m_foo 的写入尚未刷新?)。
什么样的原语(除了锁)可用于确保写入被刷新?
编辑
在我使用的代码示例中,写入和读取被放置在不同的方法中。 Thread.MemoryBarrier 不是只影响同一作用域中存在的指令重新排序吗?
另外,假设它们不会被 JIT 内联,我如何确保写入 m_foo 的值不会存储在寄存器中,而是存储在主内存中? (或者当读取 m_foo 时,它不会从 CPU 缓存中获取旧值)。
是否可以在不使用锁或“易失性”关键字的情况下实现这一目标? (另外,假设我没有使用原始类型,而是使用 WORD 大小的结构[因此无法应用 volatile]。)
When I write a value into a field, what guarantees do I get regarding when the new value will be saved in the main memory? For example, how do I know that the processor don't keep the new value in it's private cache, but updated the main memory?
Another example:
int m_foo;
void Read() // executed by thread X (on processor #0)
{
Console.Write(m_foo);
}
void Write() // executed by thread Y (on processor #1)
{
m_foo = 1;
}
Is there a possibility that after Write() was finished executing, some other thread executes Read() but actually will see "0" as the current value? (since perhaps the previous write to m_foo wasn't flushed yet?).
What kind of primitives (beside locks) are available to ensure the the writes were flushed?
EDIT
In the code sample I've used, the write and read are placed at different method. Doesn't Thread.MemoryBarrier only affect instruction reording that exist in the same scope?
Also, let's assume that they won't be inlined by the JIT, how can I make sure that the value written to m_foo won't be stored in a register, but to the main memory? (or when m_foo is read, it won't get an old value from the CPU cache).
Is it possible to achieve this without using locks or the 'volatile' keyword? (also, let's say I'm not using primitive types, but a WORD sized structs [so volatile cannot be applied].)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
如果您想确保及时且按顺序写入,请将其标记为
易失性
,或者(更痛苦)使用Thread.VolatileRead
/Thread.VolatileWrite
(不是一个有吸引力的选项,并且很容易错过一个,使其毫无用处)。否则,您几乎无法保证任何事情(一旦您谈论多线程)。
您可能还想查看锁定(
Monitor
) 或
互锁
,只要所有访问(即所有锁定
,或全部互锁
等)。If you want to ensure it is written promptly and in-order, then mark it as
volatile
, or (with more pain) useThread.VolatileRead
/Thread.VolatileWrite
(not an attractive option, and easy to miss one, making it useless).Otherwise you have virtually no guarantees of anything (as soon as you talk multiple threads).
You might also want to look at locking (
Monitor
) orInterlocked
, which would achieve the same effect as long as the same approach is used from all access (i.e. alllock
, or allInterlocked
, etc).易失性和互锁已经被提及,您要求原语,列表中的一项补充是在写入或读取之前使用 Thread.MemoryBarrier() 。这保证了内存写入和读取不会重新排序。
这是“手动”执行
lock
、Interlocked
和volatile
在大多数情况下可以自动执行的操作。你可以用它来完全替代任何其他技术,但它可以说是最难走的路,MSDN 是这样说的:如何使用 MemoryBarrier
一个非常好的例子是
VolatileRead
和VolatileWrite
的实现,它们都在内部使用MemoryBarrier
。遵循的经验法则是:当您读取变量时,请在读取之后放置内存屏障。当您写入值时,内存屏障必须位于写入之前。如果您怀疑这是否比锁定效率低,请考虑锁定只不过是“完全防护”,因为它在代码块之前和之后放置了内存屏障(忽略 Monitor for一会儿)这个原则在这篇关于线程、锁定、易失性的优秀权威文章中得到了很好的解释。和 Albahari 的内存屏障
来自反射器:
Volatile and Interlocked have already been mentioned, you asked for primitives, one addition to the list is to use
Thread.MemoryBarrier()
before your writes or reads. This guarantees no reordering is done of memory writes and reads.This is doing "by hand" what
lock
,Interlocked
andvolatile
can do automatically most of the time. You could use this as a full replacement to any other technique, but it is arguably the hardest path to travel, and so says MSDN:How to use MemoryBarrier
A very fine example are the implementations of
VolatileRead
andVolatileWrite
, that both internally useMemoryBarrier
. The basic rule of thumb to follow is: when you read a variable, place a memory barrier after the read. When you write the value, the memory barrier must come before the write.In case you've doubts whether this is less efficient then
lock
, consider that locking is nothing more then "full fencing", in that it places a memory barrier before and after the code block (ignoring Monitor for a moment). This principle is well explained in this excellent definitive article on threads, locking, volatile and memory barriers by Albahari.From reflector:
只要不使用任何同步,就无法保证一个处理器上运行的线程可以看到另一个处理器上运行的另一个线程所做的更改。这是因为该值可以缓存在 CPU 缓存或 CPU 寄存器中。
因此,您需要将变量标记为易失性。这将在读取和写入之间创建“发生之前”关系。
As long as you don't use any synchronisation you have no guarantee that a thread running on one processor sees the changes made by another thread running on another processor. That's because the value could be cached in the CPU caches or in a CPU register.
Therefore you need to either mark the variable as volatile. That will create a 'Happens-Before'-realation between reads an writes.
这不是处理器缓存问题。写入通常是直通的(写入同时写入缓存和主内存),所有读取都将访问缓存。但还有许多其他缓存(编程语言、库、操作系统、I/O< /a> 缓冲区等)。编译器还可以选择将变量保留在处理器寄存器中,并且从不将其写入主内存(这就是 易失性运算符的设计目的,当它可以是内存映射 I/O 时,避免将值存储在寄存器中)。
如果您有多个进程或多线程,并且同步是一个问题,您必须明确执行同步,根据用例,有多种方法可以执行此操作。
对于单线程程序,不必关心,编译器将执行其必须执行的操作,并且读取将访问已写入的内容。
That's not a processor cache issue. Writes are usually pass-through (writes go both to cache and main memory) and all reads will access to cache. But there is many other caches on the way (programming language, libraries, operating system, I/O buffers, etc.). The compiler can also choose to keep a variable in a processor register and to never write it to main memory (that's what the volatile operator is designed for, avoid storing value in register when it can be a memory mapped I/O).
If you have multiple processes or multiple threads and synchronisation is an issue you must do it explictly, there is many way to do it depending on the use case.
For a single threaded program, do not care, the compiler will do what it must and reads will access to what has been written.