当锁定获取顺序无法保证时,避免死锁

时间:2013-08-20 10:15:11

标签: c# multithreading locking deadlock

假设我有以下课程:

public class IntBagWithLock
{
    private readonly lockObject = new object();
    private bool assigned = false;
    private int data1;
    private int data2;

    public int? Data1
    {
        get { lock (lockObject) { return assigned ? data1 : (int?)null; } }
    }
    public int? Data2
    {
        get { lock (lockObject) { return assigned ? data2 : (int?)null; } }
    }
    public bool Assigned { get { lock(lockObject) { return assigned; } }

    public bool TrySetData(int value1, int value2)
    {
        lock (lockObject)
        {
            if (assigned) return false;

            data1 = value1;
            data2 = value2;
            assigned = true;
            return true;
        }
    }

    public bool IsEquivalentTo(IntBagWithLock other)
    {
        if (ReferenceEquals(this, other)) return true;
        if (ReferenceEquals(other, null)) return false;

        lock (lockObject)
        {
            if (!assigned) return false;
            lock (other.lockObject)
            {
                return other.assigned && other.data1 == data1 && other.data2 == data2;
            }
        }
    }
}

我担心的问题是,由于IsEquivalentTo的实现方式,如果线程调用item1.IsEquivalentTo(item2)并获得item1,我可能会遇到死锁情况。 }锁定,另一个调用item2.IsEquivalentTo(item1)并获得item2

我应该尽可能地确保不会发生此类死锁?

更新2 :代码示例已被修改为更接近我实际拥有的内容。我认为所有答案仍然有效。

4 个答案:

答案 0 :(得分:2)

通常,您为每个对象提供唯一ID,然后从较低ID锁定到较高ID:

public class BagWithLock
{
    // The first Id generated will be 1. If you want it to be 0, put
    // here -1 .
    private static int masterId = 0; 

    private readonly object locker = new object();

    private readonly int id = Interlocked.Increment(ref masterId);

    public static void Lock(BagWithLock bwl1, BagWithLock bwl2, Action action)
    {
        if (bwl1.id == bwl2.id)
        {
            // same object case
            lock (bwl1.locker)
            {
                action();
            }
        }
        else if (bwl1.id < bwl2.id)
        {
            lock (bwl1.locker)
            {
                lock (bwl2.locker)
                {
                    action();
                }
            }
        }
        else
        {
            lock (bwl2.locker)
            {
                lock (bwl1.locker)
                {
                    action();
                }
            }
        }
    }
}

你可以像使用它一样:

bool equals;

BagWithLock(bag1, bag2, () => {
    equals = bag1.SequenceEquals(bag2);
});

所以你传递Action包含你想要在lock内做的事情。

Interlocked.Increment上的static masterId保证每个班级都有唯一的id。请注意,如果您创建此类的超过40亿个实例,那么成为问题。如果您需要,请使用long

答案 1 :(得分:1)

我不知道为什么每当你达到或等于你时就会锁定,但你可以这样做:

public bool IsEquivalentTo(BagWithLock other)
{
    object myData;
    object otherData;
    lock (lockObject)
        myData = data;

    lock (other.lockObject)
        otherData = other.data;

    return object.Equals(myData, otherData);
}

这样,物品在比较时不会改变。

一般来说,这种锁有一些缺点,我认为我会做一般的静态lockObject所以你只能在一个可能是竞争条件的方法中使用对象< / p>


<强>更新 根据你的更新,我会说你应该使用:

private static readonly object equalLock = new object();

public bool IsEquivalentTo(IntBagWithLock other)
{
    lock(equalLock){
       if (ReferenceEquals(this, other)) return true;
       if (ReferenceEquals(other, null)) return false;
         if (!assigned) return false;
           return other.assigned && other.data1 == data1 && other.data2 == data2;
   }
}

答案 2 :(得分:1)

由于OP提到dataImmutable,我认为这里根本不需要锁,“volatile”应该可以解决问题。

public class BagWithLock
{
    private volatile object data;
    public object Data
    {
        get { lock return data; }
        set { data = value;  }
    }
    public bool IsEquivalentTo(BagWithLock other)
    {
        return object.Equals(data, other.data);
    }
}

这应该是线程安全的。如果我错了请纠正我。

答案 3 :(得分:0)

也许您可以使用成本更高,WaitHandle派生的锁定对象(例如Mutex),并在需要多次锁定时使用WaitHandle.WaitAll()