为实例创建多个syncLock变量
我有两个内部属性,它们使用支持字段的延迟加载,并在多线程应用程序中使用,因此我按照 这篇 MSDN 文章
现在,首先假设这是一个合适的模式,所有示例都显示为实例创建单个锁定对象。如果我的两个属性彼此独立,那么为每个属性创建一个锁实例不是更高效吗?
我突然想到,也许只有一个,以避免死锁或竞争条件。我没有想到一个明显的情况,但我确信有人可以向我展示一个......(显然,我对多线程代码不是很有经验)
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;
}
}
I have two internal properties that use lazy-loading of backing fields, and are used in a multi-threaded application, so I have implemented a double-checking lock scheme as per this MSDN article
Now, firstly assuming that this is an appropriate pattern, all the examples show creating a single lock object for an instance. If my two properties are independent of each other, would it not be more efficient to create a lock instance for each property?
It occurs to me that maybe there is only one in order to avoid deadlocks or race-conditions. A obvious situation doesn't come to mind, but I'm sure someone can show me one... (I'm not very experienced with multi-threaded code, obviously)
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;
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果您的财产确实是独立的,那么为每个财产使用独立的锁也没有什么坏处。
If your properties are truly independent, then there's no harm in using independent locks for each of them.
如果两个属性(或更具体地说是它们的初始值设定项)彼此独立,如您提供的示例代码中所示,则拥有两个不同的锁定对象是有意义的。然而,当初始化很少发生时,影响可以忽略不计。
请注意,您还应该保护设置器的代码。
lock
语句强加了所谓的内存屏障,这对于防止竞争条件尤其是在多 CPU 和/或多核系统上是必不可少的。In case the two properties (or their initializers more specifically) are independent of each other, as in the sample code you provided, it makes sense to have two different lock objects. However, when the initialization occurs rarely, the effect will be negligible.
Note that you should protect the setter's code as well. The
lock
statement imposes a so called memory barrier, which is indispensable especially on multi-CPU and/or multi-core systems to prevent race conditions.是的,如果它们彼此独立,这确实会更有效,因为对其中一个的访问不会阻止对另一个的访问。如果这种独立性被证明是错误的,那么你也会担心陷入僵局的风险。
问题是,假设
_someProperty1 = new List();
不是分配给_someProperty1
的真正代码(几乎不值得延迟加载,不是吗? ?),那么问题是:填充 SomeProperty1 的代码是否可以通过任何代码路径调用填充 SomeProperty2 的代码,反之亦然,无论多么奇怪?即使一个可以调用另一个,也不可能出现死锁,但是如果它们都可以互相调用(或者1调用2,2调用3和3调用1,等等),那么肯定会发生死锁。
通常,我会从宽锁(一个锁用于所有锁定任务)开始,然后根据需要将锁缩小作为优化。如果您有 20 个需要锁定的方法,那么判断安全性可能会更困难(而且,您开始仅用锁定对象填充内存)。
请注意,您的代码还存在两个问题:
首先,您没有锁定您的设置器。可能这很好(您只是希望锁能够防止对加载方法的多次重调用,并且实际上并不关心
set
和get< /code>),这可能是一场灾难。
其次,根据运行它的CPU,在写入时仔细检查它可能会出现读/写重新排序问题,因此您应该使用易失性字段,或者调用内存屏障。请参阅 http://blogs.msdn.com/b/ brada/archive/2004/05/12/130935.aspx
编辑:
还值得考虑它是否真的需要。
考虑到操作本身应该是线程安全的:
1和2只会发生在一个线程上,3是原子的。因此,锁定的优点是:
如果执行上述步骤 1 和/或 2 存在其自己的线程问题,并且不受其自己的锁的保护,则锁定是 100% 必要的。
如果某些东西对步骤 1 和 2 中获得的值进行操作,然后重复步骤 1 和 2 进行操作,
锁定将防止多次执行 1 和 2 的浪费。
因此,如果我们可以排除情况 1 和 2 的问题(需要进行一些分析,但通常是可能的),那么我们只需担心情况 3 中的浪费即可。现在,也许这是一个很大的担忧。然而,如果它很少出现,而且出现时也没有那么浪费,那么不锁定的收益将超过锁定的收益。
如果有疑问,锁定可能是更安全的方法,但忍受偶尔的浪费操作可能会更好。
Yes, if they are independent of each other, this would indeed be more efficient, as access to one wont' block access to the other. You're also on the money about the risk of a deadlock if that independence turned out to be false.
The question is, presuming that
_someProperty1 = new List<SomeObject1>();
isn't the real code for assigning to_someProperty1
(hardly worth the lazy-load, is it?), then the question is: Can the code that fills SomeProperty1 ever call that which fills SomeProperty2, or vice-versa, through any code-path, no matter how bizarre?Even if one can call the other, there can't be a deadlock, but if they both can call each other (or 1 call 2, 2 call 3 and 3 call 1, and so on), then a deadlock can definitely happen.
As a rule, I'd start with broad locks (one lock for all locked tasks) and then make the locks narrower as an optimisation as needed. In cases where you have, say, 20 methods which need locking, then judging the safety can be harder (also, you begin to fill memory just with lock objects).
Note that there are two issues with your code also:
First, you don't lock in your setter. Possibly this is fine (you just want your lock to prevent multiple heavy calls to the loading method, and don't actually care if there are over-writes between the
set
, and theget
), possibly this is a disaster.Second, depending on the CPU running it, double-check as you write it can have issues with read/write reordering, so you should either have a volatile field, or call a memory barrier. See http://blogs.msdn.com/b/brada/archive/2004/05/12/130935.aspx
Edit:
It's also worth considering whether it's really needed at all.
Consider that the operation itself should be thread-safe:
1 and 2 will only happen on one thread, and 3 is atomic. Therefore, the advantage of locking is:
If performing step 1 and/or 2 above have their own threading issues, and aren't protected from them by their own locks, then locking is 100% necessary.
If it would be disastrous for something to have acted upon a value obtained in step 1 and 2, and then later to do so with step 1 and 2 being repeated, locking is 100% necessary.
Locking will prevent the waste of 1 and 2 being done multiple times.
So, if we can rule out case 1 and 2 as an issue (takes a bit of analysis, but it's often possible), then we've only preventing the waste in case 3 to worry about. Now, maybe this is a big worry. However, if it would rarely come up, and also not be that much of a waste when it did, then the gains of not locking would outweigh the gains of locking.
If in doubt, locking is probably the safer approach, but its possible that just living with the occasional wasted operation is better.