为什么Interlocked.Add没有超载接受双打作为参数?

Dan*_*Tao 20 .net c# vb.net multithreading atomic

我完全理解Threading.Interlocked类提供的原子性; 但是,我不明白为什么Add函数只提供两个重载:一个用于Integers,另一个用于Longs.为什么不双打,或任何其他数字类型?

显然,更改Double的预期方法是CompareExchange; 我认为这是因为修改Double比修改Integer更复杂.我还不清楚为什么,如果CompareExchange和Add都能接受整数,他们也不能同时接受双打.

Eug*_*sky 32

其他人已经解决了"为什么?".然而Add(ref double, double),使用CompareExchange原语很容易滚动自己:

public static double Add(ref double location1, double value)
{
    double newCurrentValue = location1; // non-volatile read, so may be stale
    while (true)
    {
        double currentValue = newCurrentValue;
        double newValue = currentValue + value;
        newCurrentValue = Interlocked.CompareExchange(ref location1, newValue, currentValue);
        if (newCurrentValue == currentValue)
            return newValue;
    }
}
Run Code Online (Sandbox Code Playgroud)

CompareExchange设置的值location1newValue,如果当前值等于currentValue.正如它以原子,线程安全的方式这样做,我们可以单独依赖它而不需要求助于锁.

为什么while (true)循环?在实现乐观并发算法时,这样的循环是标准的.如果当前值不同,CompareExchange则不会改变.我初始化为- 执行非易失性读取(可能是陈旧的,但这不会改变正确性,因为将检查值).如果当前值(仍然)是我们从中读取的值,则将值更改为.如果没有,我们必须重新使用新的当前值,如返回的那样.location1currentValuecurrentValuelocation1CompareExchangelocationCompareExchangenewValueCompareExchangeCompareExchange

如果该值被另一个线程更改,直到我们CompareExchange再次下一次,它将再次失败,需要再次重试 - 这理论上可以永远继续,因此循环.除非您不断更改多个线程的值,CompareExchange否则很可能只调用一次,如果当前值仍然是非易失性读取location1产生的值,或者两次,如果它不同.

  • 这是一个很好的答案!至少现在我想我知道乐观并发意味着什么:'编写并发代码时假设事情最终会正确.' 在我们的情况下(真实)这意味着我们不会永远循环(我们相当乐观).非常感谢! (4认同)
  • @EvgeniyBerezovsky:在 IEEE FP 语义中,“NaN == NaN”始终为 false,即使它们具有相同的位模式。如果你的函数只能通过对“double”进行“==”比较来返回“true”,那么这始终是一个问题。与“CompareExchange”更改位模式无关,只是“Interlocked.CompareExchange”不返回单独的“bool”这一事实,因此您必须从它产生的一个输出中合成它。(与 C++11 `compare_exchange_weak`/`strong` 不同,C++11 通过引用 CAS 失败来更新“预期”参数,并返回 `bool` 状态。) (3认同)
  • 我想这有一个极端的情况,你最终会陷入无限循环:如何最好地解决“location1”为“NaN”的情况? (2认同)
  • @Dr.Strangelove我想你可以使用 [`Equals`](https://learn.microsoft.com/en-us/dotnet/api/system.double.equals) 方法而不是`==`: `if (newCurrentValue.Equals(currentValue))`。-- *“如果通过调用“Equals”方法测试两个“Double.NaN”值是否相等,则该方法返回“true”。”* (2认同)
  • @Dr.Strangelove(感谢您的详细阐述,Theodor)。我刚刚测试了 Strangelove 博士的断言,他是对的:`double d1 = Double.NaN; Add(ref d1, 2);` 永远旋转。正如 Theodor 所建议的,使用 `Equals()` 而不是 `==` 可以解决问题。所以看起来 `CompareExchange` 不只是将 newValue 复制到 location1 并按原样返回它,而且 `newValue` 和 `newCurrentValue` 最终会变成 `!=`,而是 `Equals()`。我相应地改变了我的答案。 (2认同)

Ree*_*sey 25

Interlocked类包含Windows API Interlocked**函数.

反过来,它们使用x86的LOCK指令前缀环绕本机处理器API.它仅支持为以下指令添加前缀:

BT,BTS,BTR,BTC,XCHG,XADD,ADD,OR,ADC,SBB,AND,SUB,XOR,NOT,NEG,INC,DEC

你会注意到,这些反过来几乎映射到互锁的方法.不幸的是,这里不支持非整数类型的ADD函数.64位平台支持64位长的添加.

这是一篇很棒的文章,讨论了指令级别的锁语义.

  • 该链接不再有效.这是Internet Archive的备份https://web.archive.org/web/20160319061137/http://www.codemaestro.com/reviews/8 (2认同)

小智 7

正如Reed Copsey所说,Interlocked操作映射(通过Windows API函数)到x86/x64处理器直接支持的指令.鉴于其中一个函数是XCHG,您可以执行原子XCHG操作,而无需真正关心目标位置的位代表什么.换句话说,代码可以"假装",你正在交换的64位浮点数字实际上是一个64位整数,XCHG指令不会知道其中的差别.因此,.Net可以通过"假装"它们分别是整数和长整数来为浮点数和双精度数提供Interlocked.Exchange函数.

然而,所有其他的操作其实也目的地的各个位进行操作,所以他们将无法正常工作,除非值实际上代表整数(或比特串在某些情况下).