当无法保证锁的获取顺序时,避免死锁
本文关键字:顺序 死锁 获取 | 更新日期: 2023-09-27 18:28:15
假设我有以下类:
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
,那么我可能会遇到死锁情况。
我应该怎么做才能尽可能确保这种僵局不会发生?
UPDATE 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
s中执行的操作。
static masterId
上的Interlocked.Increment
保证每个类都有一个唯一的id
。请注意,如果您创建了超过40亿个此类实例,则将出现问题。如果需要,请使用long
。
由于OP提到的data
是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);
}
}
这应该是线程安全的。如果我错了,请纠正我。
我不知道为什么每次获得或等于时都会锁定,但你可以做到:
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;
}
}
也许您可以使用成本更高的WaitHandle
派生的锁定对象(如Mutex),并在需要同时使用多个锁时使用WaitHandle.WaitAll()
?