是否有使用Interlocked.Exchange
API替换此代码?
if (IsWorking == false)
{
lock (this)
{
if (IsWorking == false)
{
IsWorking = true;
}
}
}
答案 0 :(得分:2)
您通常会使用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
:
“如果comparand和location1中的值相等,则value存储在location1中。否则,不执行任何操作。比较和交换操作作为原子操作执行.RealExExchange的返回值是原始值。 location1,无论是否进行交换。“
答案 1 :(得分:1)
首先,IsWorking = true
将是等效的,除非在执行此操作时有其他并发代码持有锁。像这样的原始值的赋值保证是原子的。显然,条件和分配的组合可能不是原子的。但是,似乎只有当IsWorking现在为假时,代码才会将IsWorking设置为true。但是,如果IsWorking现在是真的,将它重新设置为True会有什么危害?看起来你错过了一些想要一种线程安全的方式来通知外界的状态变化的东西。您可以在此处使用事件或监视器。
您可能也在寻找Interlocked.CompareExchange
,但这仅适用于引用类型和原始数字类型。因此,您需要将布尔值更改为int。但是,CompareExchange方法不会等待您的锁定。它将简单地返回旧值,无论它是否被替换。
因此,如果您将IsWorking从布尔属性更改为int字段,则可以:
int wasWorking = Interlocked.CompareExchange(ref isWorking, 1, 0);
如果wasWorking为0,你知道你改变了状态,如果wasWorking是1,你知道你没有改变状态。
答案 2 :(得分:1)
是的,但它是否有用是另一回事。
您可以用整数替换isWorking
,然后使用Interlocked.CompareExchange(ref isWorking, 1, 0)
。
这是毫无意义的;无论哪种方式,最后的结果是isWorking
是1
,所以我们只需用IsWorking = true
(或者可能是VolatileWrite
替换代码来确保更好的并发性被其他CPU看到了。)
您的代码可能会减少类似:
if (isWorking == false)
lock (this)
if (isWorking == false)
{
DoSomethingWorthDoing();
isWorking = true;
}
而DoSomethingWorthDoing()
部分就会崩溃。
有一些方法可以改善这种双重检查锁的并发性,但这取决于一些事情。一个例子是:
if(someUsefulThing == null)
Interlocked.CompareExchange(ref someUsefulThing, SomeUsefulFactory(), null);
此someUsefulThing
的结尾将设置为SomeUsefulFactory()
的结果,设置后将不会再次设置。虽然有一段时间我们可能多次调用SomeUsefulFactory()
只是为了抛弃结果。有时这是一场灾难,有时候这很好,有时候我们可能没有锁定而且很好;这取决于我们为什么要在这里分享同一个对象。
还有其他变体,但适用性取决于您关注并发的原因。例如,This code使用这样的互锁操作实现了一个线程安全的并发字典,这种情况往往比争用率低时仅仅在lock
周围访问字典慢,但是当许多线程时更好同时访问它。