class MyClass { private readonly int a; private int b; public MyClass(int a,int b) { this.a = a; this.b = b; } public int A { get { return a; } } public int B { get { return b; } } }
我可以以多线程方式使用这个类:
MyClass value = null; Task.Run(() => { while (true) { value = new MyClass(1,1); Thread.Sleep(10); } }); while (true) { MyClass result = value; if (result != null && (result.A != 1 || result.B != 1)) { throw new Exception(); } Thread.Sleep(10); }
我的问题是:我会看到这个(或其他类似的多线程代码)抛出异常吗?我经常看到参考非易失性写入可能不会立即被其他线程看到的事实.因此,似乎这可能会失败,因为写入值字段可能会在写入和b之前发生.这是可能的,还是在内存模型中有什么可以使这个(相当常见的)模式安全吗?如果是这样,那是什么?这个目的吗?如果a和b是不能被原子地编写的类型(例如,一个自定义结构体),那么它会很重要吗?
解决方法
发布语义:确保在栅栏之前没有加载或存储
将围栏后移动.之后的说明可能还会发生
围栏(取自CPOW第512页).
这意味着在引用类引用后,构造函数初始化不能被移动.
乔·达菲在article about the very same subject年提到这一点.
Rule 2: All stores have release semantics,i.e. no load or store may
move after one.
Vance morrison的article here也证实了这一点(Section Technique 4:Lazy Initialization).
Like all techniques that remove read locks,the code in Figure 7
relies on strong write ordering. For example,this code would be
incorrect in the ECMA memory model unless myValue was made volatile
because the writes that initialize the LazyInitClass instance might be
delayed until after the write to myValue,allowing the client of
GetValue to read the uninitialized state. In the .NET Framework 2.0
model,the code works without volatile declarations.
写入保证按照从CLR 2.0开始的顺序发生.它没有在ECMA标准中指定,只是CLR的微软实现给了这个保证.如果您在CLR 1.0或CLR的任何其他实现中运行此代码,您的代码可能会中断.
这个变化背后的故事是:(从CPOW第516页)
When the CLR 2.0 was ported to IA64,its initial development had
happened on X86 processors,and so it was poorly equipped to deal with
arbitrary store reordering (as permitted by IA64) . The same was true
of most code written to target .NET by nonMicrosoft developers
targeting WindowsThe result was that a lot of code in the framework broke when run on
IA64,particularly code having to do with the infamous double-checked
locking pattern that suddenly didn’t work properly. We’ll examine this
in the context of the pattern later in this chapter. But in summary,
if stores can pass other stores,consider this: a thread might
initialize a private object’s fields and then publish a reference to
it in a shared location; because stores can move around,another
thread might be able to see the reference to the object,read it,and
yet see the fields while they are still i n an uninitialized state.
Not only did this impact existing code,it could violate type system
properties such as initonly fields.So the CLR architects made a decision to strengthen 2.0 by emitting
all stores on IA64 as release fences. This gave all CLR programs
stronger memory model behavior. This ensures that programmers needn’ t
have to worry about subtle race conditions that would only manifest in
practice on an obscure,rarely used and expensive architecture.
注意Joe duffy说,他们通过将IA64上的所有商店作为释放栅栏来加强2.0,这并不意味着其他处理器可以重新排序.其他处理器本身固有的保证了商店(商店之后的商店)将不被重新排序.所以CLR不需要明确的保证这一点.