3

假设我有以下课程:

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)item1item2.IsEquivalentTo(item1)item2

我应该怎么做才能尽可能确保不会发生这种死锁?

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

4

4 回答 4

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包含你想要在locks 中做的事情。

Interlocked.Incrementon a保证每个类static masterId都是唯一的。id请注意,如果您创建超过 40 亿个此类的实例,则会出现问题。如果您需要这样做,请使用long.

于 2013-08-20T10:30:58.697 回答
1

由于提到的 OP datais Immutable,我认为这里根本不需要锁,'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);
    }
}

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

于 2013-08-20T10:44:42.517 回答
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,所以你一次只能在一个可能是竞争条件的方法中拥有一个对象


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

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;
   }
}
于 2013-08-20T10:20:08.230 回答
0

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

于 2013-08-20T10:20:27.993 回答