And*_*ell 15 c# memory-model thread-safety memory-barriers mesi
在C#中,这是以线程安全方式调用事件的标准代码:
var handler = SomethingHappened;
if(handler != null)
handler(this, e);
Run Code Online (Sandbox Code Playgroud)
其中,可能在另一个线程上,编译器生成的add方法用于Delegate.Combine创建新的多播委托实例,然后在编译器生成的字段上设置该实例(使用互锁比较交换).
(注意:出于这个问题的目的,我们不关心在事件订阅者中运行的代码.假设它在删除时是线程安全且健壮的.)
在我自己的代码中,我想按照以下方式做类似的事情:
var localFoo = this.memberFoo;
if(localFoo != null)
localFoo.Bar(localFoo.baz);
Run Code Online (Sandbox Code Playgroud)
哪里this.memberFoo可以由另一个线程设置.(这只是一个线程,所以我不认为它需要联锁 - 但也许这里有副作用?)
(并且,显然,假设它Foo是"不可变的",我们在这个线程上使用它时不会主动修改它.)
现在我理解这是线程安全的明显原因:从引用字段读取是原子的.复制到本地可确保我们不会获得两个不同的值.(显然只能从.NET 2.0保证,但我认为它在任何理智的.NET实现中都是安全的吗?)
但我不明白的是:被引用的对象实例所占用的内存如何?特别是在缓存一致性方面?如果"writer"线程在一个CPU上执行此操作:
thing.memberFoo = new Foo(1234);
Run Code Online (Sandbox Code Playgroud)
什么保证Foo分配新内存的内存不会出现在"读取器"运行的CPU的缓存中,具有未初始化的值?什么确保localFoo.baz(上面)不读取垃圾?(跨平台的保证有多好?在Mono上?在ARM上?)
如果新创建的foo恰好来自游泳池呢?
thing.memberFoo = FooPool.Get().Reset(1234);
Run Code Online (Sandbox Code Playgroud)
从内存的角度来看,这似乎没有什么不同,只是一个新的分配 - 但是.NET分配器可能会让第一个案例有效吗?
在我提出这个问题时,我的想法是需要一个内存屏障来确保 - 不是因为读取依赖而不能移动内存访问 - 而是作为CPU的一个信号来清除任何缓存失效.
我的来源是维基百科,所以你要做的就是这样.
(我可能会猜测,也许在连动比较交换作家线程上无效缓存读者?或者,也许所有的读取原因失效吗?或者指针引用引起失效?我特别关注如何平台特有的这些东西的声音.)
更新:只是为了更明确地说明问题是关于CPU缓存失效以及.NET提供的保证(以及这些保证可能如何依赖于CPU架构):
Q(内存位置)中存储了引用.R,并写入到基准R成QQ,并获取内存位置RR假设GC不会在任何时候运行.没有其他有趣的事情发生.
问题:在A初始化期间修改它之前,什么阻止R进入B的缓存中,这样当B从中读取它时会得到陈旧的值,尽管它得到了一个新版本,知道首先在哪里? RQR
(可替代的措词:什么使修改R可见CPU 乙处或点,该改变之前Q是CPU可见乙).
(并且这仅适用于分配给new或任何内存的内存吗?)+
我想我已经知道答案是什么了。但我不是硬件专家,所以我愿意接受更熟悉 CPU 工作原理的人的纠正。
\n\n.NET 2.0 内存模型保证:
\n\n\n\n\n写入操作不能超过同一线程的其他写入操作。
\n
这意味着写入CPU(示例中的A)永远不会将对对象的引用写入内存(to Q),直到它写出正在构造的该对象的内容(to R)。到目前为止,一切都很好。不能重新排序:
R = <data>\nQ = &R\nRun Code Online (Sandbox Code Playgroud)\n\n让我们考虑读取 CPU ( B )。R在读取 from 之前如何阻止它读取Q?
R在足够 na\xc3\xafve CPU 上,人们会认为如果不先从 读取,就不可能读取Q。我们必须首先读取Q以获取 的地址R。(注意:可以安全地假设 C# 编译器和 JIT 的行为方式是这样的。)
但是,如果读取CPU有缓存,它的缓存中是否不能有陈旧的内存R,但可以接收更新的内存Q?
答案似乎是否定的。对于健全的缓存一致性协议,失效是作为队列实现的(因此称为“失效队列”)。所以在失效R之前总会先失效Q。
显然,唯一不存在这种情况的硬件是 DEC Alpha(根据此处的表 1)。它是唯一列出的可以重新排序相关读取的架构。(进一步阅读。)
\n