【发布时间】:2014-04-05 16:05:49
【问题描述】:
我已经为这个问题苦苦挣扎了大约 24 小时,但我似乎找不到解决办法。
假设我有一个 Foo 对象的集合:
public class Foo {
public int SomeValue;
public void Execute() {
lock (this) {
SomeValue++;
}
}
}
// Instantiated elsewhere and filled with lots of Foos
private static ConcurrentCollection<Foo> fooCollection;
现在我像这样为每个 Foo 调用 Execute(我有一台多核机器):
Parallel.ForEach(fooCollection, foo => foo.Execute());
现在假设我们添加第二种类型的Foo:
public class Bar : Foo {
private Foo myFriend;
public new void Execute() {
lock (this) {
SomeValue++;
lock (myFriend) {
myFriend.SomeValue += SomeValue;
}
}
}
}
这个新的Bar 在Execute 期间仍会自我锁定以保持一致性,并递增SomeValue。但它也会在其Foo 朋友上获得锁,因此myFriend 在更新myFriend.SomeValue 之前也以线程安全的方式更新。
我的问题是我可能有一个Bars 的循环引用链,例如
bar1.myFriend = bar2; bar2.myFriend = bar1;
在最坏的情况下:
- bar1.Execute() 锁定在 bar1
- bar2.Execute() 锁定在 bar2
- bar1.Execute() 等待锁定 bar2
- bar2.Execute() 等待锁定 bar1
- 死锁:(
所以,我的问题是:我该如何解决这个问题?
关于我的应用程序的一些实际指标:
- 我真正的 'Execute()' 方法所做的事情比这个例子更有意义 - 请只是这个例子的表面价值:)
- 我的收藏中有大约 50,000 个“Foos”。
Execute()由 N 个线程在每个线程上并行调用,其中 N = 主机上的逻辑内核数。 - 预期的争用率(例如,一个 Foo 正在等待另一个 Foo)很低,可能为 5%。如果没有循环引用的潜力,这个问题会很容易。速度并不是争用访问的真正问题,只是死锁情况。
一些注意事项:
- 在本例中,我使用了
lock(Monitor.Enter/Exit),但可以使用任何线程同步构造(例如状态变量和Interlocked方法等)。 - 应用程序对性能很敏感 - 所以对于 uncontended 情况,我需要尽可能快的速度。
我希望有一些我不知道的标准方法来解决这个问题。我尝试了各种体操,但每次我写出最坏的情况时,僵局仍然是潜在的。
提前致谢。 :)
【问题讨论】:
-
是的,那会死锁。您必须使用 Monitor.TryEnter() 超时,以便您可以在失败时退避并让其他线程有机会完成。最坏情况下的性能不好。
-
你不应该使用
lock(this)。阅读Why it's bad...
标签: c# multithreading deadlock