我有两个内部属性,这两个属性使用延迟加载支持字段,并在多线程应用程序中使用,因此我按照this MSDN article实现了一个双重检查锁方案
现在,首先假设这是一个合适的模式,所有的示例都显示了为一个实例创建一个锁对象。如果我的两个属性彼此独立,那么为每个属性创建一个锁实例不是更有效率吗?
我突然想到,为了避免死锁或竞争条件,可能只有一个。明显的情况不会出现在我的脑海中,但我相信有人可以给我看一个…(显然,我对多线程代码不是很有经验)
private List<SomeObject1> _someProperty1;
private List<SomeObject2> _someProperty2;
private readonly _syncLockSomeProperty1 = new Object();
private readonly _syncLockSomeProperty2 = new Object();
internal List<SomeObject1> SomeProperty1
{
get
{
if (_someProperty1== null)
{
lock (_syncLockSomeProperty1)
{
if (_someProperty1 == null)
{
_someProperty1 = new List<SomeObject1>();
}
}
}
return _someProperty1;
}
set
{
_someProperty1 = value;
}
}
internal List<SomeObject2> SomeProperty2
{
get
{
if (_someProperty2 == null)
{
lock (_syncLockSomeProperty2)
{
if (_someProperty2 == null)
{
_someProperty2 = new List<SomeObject2>();
}
}
}
return _someProperty2;
}
set
{
_someProperty2 = value;
}
}发布于 2011-03-25 01:29:49
是的,如果它们是相互独立的,这确实会更有效率,因为访问一个不会阻止对另一个的访问。如果这种独立性被证明是错误的,那么你也有陷入僵局的风险。
问题是,假设_someProperty1 = new List<SomeObject1>();不是赋值给_someProperty1的真正代码(几乎不值得延迟加载,不是吗?),那么问题是:填充SomeProperty1的代码是否可以通过任何代码路径调用填充SomeProperty2的代码,反之亦然,无论代码路径有多奇怪?
即使其中一个可以调用另一个,也不会有死锁,但是如果它们都可以调用对方(或者1个调用2,2个调用3,3个调用1,依此类推),那么肯定会发生死锁。
通常,我会从宽锁开始(一个锁用于所有锁定的任务),然后根据需要优化锁的范围。在你有20个方法需要锁定的情况下,判断安全性可能会更难(而且,你开始只用锁对象填充内存)。
请注意,您的代码还有两个问题:
首先,你没有锁定你的二传手。这可能是好的(你只想让你的锁阻止对加载方法的多次繁重调用,而实际上并不关心set和get之间是否有重写),可能这是一场灾难。
其次,根据运行它的CPU,在写入时仔细检查可能会出现读/写重新排序的问题,因此您应该使用易失性字段,或者调用内存屏障。请参阅http://blogs.msdn.com/b/brada/archive/2004/05/12/130935.aspx
编辑:
这也值得考虑是否真的需要它。
考虑到操作本身应该是线程安全的:
1和2只会发生在一个线程上,3是原子的。因此,锁定的优点是:
因此,如果我们可以排除情况1和2作为一个问题(需要一些分析,但这通常是可能的),那么我们只需要防止情况3中的浪费。现在,也许这是一个很大的担忧。然而,如果它很少出现,而且当它出现时也不是那么浪费,那么不加锁的好处将超过加锁的好处。
如果有疑问,锁定可能是更安全的方法,但也有可能只是生活在偶尔浪费的操作更好。
发布于 2011-03-25 01:19:23
如果你的属性是真正独立的,那么对每个属性使用独立的锁也没什么坏处。
发布于 2011-03-25 01:21:07
如果这两个属性(或者更具体地说是它们的初始化器)彼此独立,就像您提供的示例代码中那样,使用两个不同的锁对象是有意义的。但是,当初始化很少发生时,影响将可以忽略不计。
注意,您还应该保护setter的代码。lock语句设置了所谓的内存屏障,这在多CPU和/或多核系统中是必不可少的,以防止竞争条件。
https://stackoverflow.com/questions/5422919
复制相似问题