提问者:小点点

无法保证锁的获取顺序时避免死锁


假设我有以下类:

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:代码示例已被修改为更接近我实际拥有的内容。我认为所有答案仍然有效。


共3个答案

匿名用户

通常你给每个对象一个唯一的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,其中包含您想在中执行的操作。

静态master Id上的Interlock.增量保证了每个类都有一个唯一的id。请注意,如果您创建了该类的40亿多个实例,就会出现问题。如果需要这样做,请使用long

匿名用户

由于OP提到的dataImmutable,我认为这里根本不需要锁,“易失性”应该可以做到这一点。

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);
    }
}

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

匿名用户

我不知道你为什么每次都锁,但是你可以这样做:

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,这样你一次只能在一个可能是竞争条件的方法中拥有on object

根据您的更新更新,我会说您应该使用:

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;
   }
}