用联锁操作替换锁
本文关键字:替换 操作 | 更新日期: 2023-09-27 18:32:00
有没有使用Interlocked.Exchange
API替换此代码?
if (IsWorking == false)
{
lock (this)
{
if (IsWorking == false)
{
IsWorking = true;
}
}
}
您通常会使用Interlocked.CompareExchange
来执行此操作。但不幸的是,没有接受布尔值的重载,object
和泛型重载仅适用于引用类型。但是,您可以破解它并使用只有 2 个值(0 和 1)的int
:
private static int IsWorking = 0;
private static void Main()
{
var originalValue = Interlocked.CompareExchange(ref IsWorking, 1, 0);
}
顾名思义,Interlocked.CompareExchange
比较 2 个值(IsWorking
和 0),如果它们相等,则将另一个值 (1) 存储在原始位置。返回值是调用此原子方法之前原始位置的值。因此,如果返回 0,则调用将替换 IsWorking
中的值,如果它是 1,则另一个线程首先到达那里。
关于Interlocked.CompareExchange
:
"如果比较和位置 1 中的值相等,则值存储在位置 1 中。否则,不执行任何操作。比较和交换操作作为原子操作执行。CompareExchange 的返回值是 location1 中的原始值,无论交换是否发生。
是的,但它是否有用是另一回事。
您可以将isWorking
替换为整数,然后使用 Interlocked.CompareExchange(ref isWorking, 1, 0)
。
不过这是没有意义的;无论哪种方式,最后的结果都是isWorking
1
,所以我们可以通过用IsWorking = true
替换代码来更好地并发(或者可能是确保它被其他CPU看到的VolatileWrite
)。
您的代码可能是以下内容的减少:
if (isWorking == false)
lock (this)
if (isWorking == false)
{
DoSomethingWorthDoing();
isWorking = true;
}
这是掉下来的DoSomethingWorthDoing()
部分。
有一些方法可以提高这种双重检查锁的并发性,但这取决于一些因素。一个例子是:
if(someUsefulThing == null)
Interlocked.CompareExchange(ref someUsefulThing, SomeUsefulFactory(), null);
在此结束时someUsefulThing
将设置为SomeUsefulFactory()
的结果,设置后将不再设置。不过,在一段时间内,我们可能会多次打电话给SomeUsefulFactory()
只是为了抛弃结果。有时这是一场灾难,有时这很好,有时我们可以不锁定并且没事;这取决于我们为什么关心在这里共享同一个对象。
还有其他变体,但适用性取决于您关心并发的原因。例如,此代码使用此类互锁操作实现线程安全的并发字典,这往往比在争用较低时仅lock
访问字典要慢,但当许多线程同时访问字典时要好得多。
首先,IsWorking = true
将是等效的,除非在执行此命令时有其他并发代码持有锁。像这样的基元值的赋值保证是原子的。显然,条件和同位的组合可能不是原子的。但是,代码似乎希望仅在 IsWorking 现在为假时才将 IsWorking 设置为 true。但是,如果 IsWork 现在是真的,那么将其重新设置为 True 会有什么害处?似乎您缺少一些想要一种线程安全的方式来通知外界状态更改的东西。您可以在此处使用事件或监视器。
您可能还在寻找Interlocked.CompareExchange
但这仅适用于引用类型和基元数值类型。因此,例如,您需要从布尔值更改为整数。但是,比较交换方法不会像您的锁那样等待。它将简单地返回旧值,无论它是否被替换。
因此,如果您将 IsWorking 从布尔属性更改为 int 字段,您可以:
int wasWorking = Interlocked.CompareExchange(ref isWorking, 1, 0);
如果 wasWorking 是 0,你知道你更改了状态,如果 wasWorking 是 1,你知道你没有改变状态。