深度不可变类型的延迟初始化是否需要锁定? [英] Is a lock required with a lazy initialization on a deeply immutable type?
问题描述
如果我有一个高度不可变的类型(所有成员都是只读的,并且它们都是引用类型的成员,那么它们也都引用了高度不可变的对象).
If I have a deeply immutable type (all members are readonly and if they are reference type members, then they also refer to objects that are deeply immutable).
我想在类型上实现一个惰性的初始化属性,如下所示:
I would like to implement a lazy initialized property on the type, like this:
private ReadOnlyCollection<SomeImmutableType> m_PropName = null;
public ReadOnlyCollection<SomeImmutableType> PropName
{
get
{
if(null == m_PropName)
{
ReadOnlyCollection<SomeImmutableType> temp = /* do lazy init */;
m_PropName = temp;
}
return m_PropName;
}
}
据我所知:
m_PropName = temp;
...是线程安全的.我不太担心两个线程同时竞速初始化,因为这很少见,从逻辑的角度来看,两个结果都是相同的,如果我没有,我宁愿不使用锁
...is threadsafe. I'm not worried too much about two threads both racing to initialize at the same time, because it will be rare, both results would be identical from a logical perspective, and I'd rather not use a lock if I don't have to.
这项工作有效吗?优点和缺点是什么?
Will this work? What are the pros and cons?
修改: 感谢您的回答.我可能会继续使用锁.但是,令我惊讶的是,没有人提出编译器意识到temp变量是不必要的可能性,而直接将其分配给m_PropName.如果真是这样,那么读取线程可能会读取尚未完成构造的对象.编译器是否可以防止这种情况发生?
Thanks for your answers. I will probably move forward with using a lock. However, I'm surprised nobody brought up the possibility of the compiler realizing that the temp variable is unnecessary, and just assigning straight to m_PropName. If that were the case, then a reading thread could possibly read an object that hasn't finished being constructed. Does the compiler prevent such a situation?
(答案似乎表明运行时不允许这种情况发生.)
(Answers seem to indicate that the runtime won't allow this to happen.)
修改: 因此,我决定采用受本文的作者启发的互锁CompareExchange方法乔·达菲(Joe Duffy).
So I've decided to go with an Interlocked CompareExchange method inspired by this article by Joe Duffy.
基本上:
private ReadOnlyCollection<SomeImmutableType> m_PropName = null;
public ReadOnlyCollection<SomeImmutableType> PropName
{
get
{
if(null == m_PropName)
{
ReadOnlyCollection<SomeImmutableType> temp = /* do lazy init */;
System.Threading.Interlocked(ref m_PropName, temp, null);
}
return m_PropName;
}
}
这应该确保在此对象实例上调用此方法的所有线程都将获得对同一对象的引用,因此==运算符将起作用.可能会浪费工作,这很好,它只是使它成为一种乐观算法.
This is supposed to ensure that all threads that call this method on this object instance will get a reference to the same object, so the == operator will work. It is possible to have wasted work, which is fine - it just makes this an optimistic algorithm.
如下面的一些评论所述,这取决于.NET 2.0内存模型的工作.否则,应将m_PropName声明为volatile.
As noted in some comments below, this depends on the .NET 2.0 memory model to work. Otherwise, m_PropName should be declared volatile.
推荐答案
这将起作用.正如规范. 这可能仍然不是一种好方法,因为您的代码在调试和读取时会更加混乱,以换取对性能的轻微影响.
That will work. Writing to references in C# is guaranteed to be atomic, as described in section 5.5 of the spec. This is still probably not a good way to do it, because your code will be more confusing to debug and read in exchange for a probably minor effect on performance.
Jon Skeet在C#中实现页面很棒.
Jon Skeet has a great page on implementing singeltons in C#.
除非有分析器告诉您此代码是一个热点,否则不要进行此类小型优化的一般建议.另外,您应该警惕编写大多数程序员不检查规范就无法完全理解的代码.
The general advice about small optimizations like these is not to do them unless a profiler tells you this code is a hotspot. Also, you should be wary of writing code that cannot be fully understood by most programmers without checking the spec.
如评论中所述,即使您说您不介意是否创建了对象的2个版本,这种情况也是如此违反直觉,以至于永远不要使用这种方法.
As noted in the comments, even though you say you don't mind if 2 versions of your object get created, that situation is so counter-intuitive that this approach should never be used.
这篇关于深度不可变类型的延迟初始化是否需要锁定?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!