防止经典僵局

时间:2014-04-05 16:05:49

标签: c# multithreading deadlock

我已经在这个问题上苦苦挣扎了大约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

我遇到的问题是我可能有Bar s的循环引用链,例如

bar1.myFriend = bar2; bar2.myFriend = bar1;

在最坏的情况下:

  1. bar1.Execute()锁定 bar1
  2. bar2.Execute()锁定 bar2
  3. bar1.Execute()等待锁定 bar2
  4. bar2.Execute()等待锁定 bar1
  5. 死锁:(

  6. 所以,我的问题是:我如何解决这个问题?

    有关我的应用的一些实际指标:

    • 我的真实&#39;执行()&#39;方法做了比这个例子更有意义的事情 - 请在面值上这个例子:)
    • 我有大约50,000个&#39; Foos&#39;在我的收藏中。 N个线程并行调用Execute(),其中N =主机上的逻辑核心数。
    • 预期的争用率(例如,Foo在另一个Foo上等待的位置)很低,可能是5%。如果不是循环引用的潜力,问题将很容易。速度并不是争用访问的关注点,只是死锁情况。

    一些警告:

    • 在这个例子中,我使用了lockMonitor.Enter/Exit),但是可以使用任何线程同步构造(例如状态变量&amp; Interlocked方法等)。 / LI>
    • 该应用程序对性能非常敏感 - 因此对于无竞争情况,我需要尽可能多的速度。

    我希望有一些解决这个问题的标准方法,我并不知道。我尝试过各种各样的体操,但每次我写出最糟糕的情况时,僵局总是有潜力。

    提前致谢。 :)

1 个答案:

答案 0 :(得分:2)

好吧,通常为了避免死锁,我们可以优先考虑资源,所以总是先尝试锁定优先级较高的资源。

在你的情况下,它不像这里那么简单,对象是相同的或派生类型。

因此,解决此问题的一种非常扭曲的方法是为所有实例提供自动增量整数。任何具有更高价值的实例都将被视为具有更高的优先级

类似


foo p1 = this;
foo p2 = myfriend;
if(p1.priority < p2.priority)
{
    foo t = p2;
    p2 = p1;
    p1 = t;
}
lock(p1)
{
    lock(p2)
    {
        // here you can safely increment both p1 & p2 some value. 

    }
}