为什么没有接受 Doubles 作为参数的 Interlocked.Add 重载?

发布于 2024-08-04 11:10:02 字数 243 浏览 7 评论 0原文

我完全欣赏 Threading.Interlocked 类提供的原子性;但我不明白为什么 Add 函数只提供两种重载:一种用于整数,另一种用于长整型。为什么不使用双精度数或任何其他数字类型呢?

显然,更改 Double 的预期方法是 CompareExchange;我猜测这是因为修改 Double 是比修改 Integer 更复杂的操作。我仍然不清楚为什么,如果 CompareExchange 和 Add 都可以接受整数,那么它们不能同时接受双精度数。

I fully appreciate the atomicity that the Threading.Interlocked class provides; I don't understand, though, why the Add function only offers two overloads: one for Integers, another for Longs. Why not Doubles, or any other numeric type for that matter?

Clearly, the intended method for changing a Double is CompareExchange; I am GUESSING this is because modifying a Double is a more complex operation than modifying an Integer. Still it isn't clear to me why, if CompareExchange and Add can both accept Integers, they can't also both accept Doubles.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

晨光如昨 2024-08-11 11:10:02

其他人已经解决了“为什么?”。然而,使用 CompareExchange 原语来创建自己的 Add(ref double, double) 是很容易的:

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.Equals(currentValue)) // see "Update" below
            return newValue;
    }
}

CompareExchange 设置 的值如果当前值等于 currentValue,则 location1newValue。由于它以原子、线程安全的方式执行此操作,因此我们可以单独依赖它,而无需求助于锁。

为什么要使用 while (true) 循环?在实现乐观并发算法时,这样的循环是标准的。如果当前值与 currentValue 不同,CompareExchange 不会更改 location1。我将 currentValue 初始化为 location1 - 进行非易失性读取(这可能是过时的,但这不会改变正确性,因为 CompareExchange 会检查值)。如果当前值(仍然)是我们从 location1 读取的值,CompareExchange 会将值更改为 newValue。如果不是,我们必须使用 CompareExchange 返回的新当前值重试 CompareExchange

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


更新 2022/8/17

正如 Strangelove 博士和 Theodor Zoulias 在评论中指出的,当 location1 == Double.NaN 时,Add() 将变成无限循环

所以我不得不

if (newCurrentValue == currentValue)

改为

if (newCurrentValue.Equals(currentValue))

Others have addressed the "why?". It is easy however to roll your own Add(ref double, double), using the CompareExchange primitive:

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.Equals(currentValue)) // see "Update" below
            return newValue;
    }
}

CompareExchange sets the value of location1 to be newValue, if the current value equals currentValue. As it does so in an atomic, thread-safe way, we can rely on it alone without resorting to locks.

Why the while (true) loop? Loops like this are standard when implementing optimistically concurrent algorithms. CompareExchange will not change location1 if the current value is different from currentValue. I initialized currentValue to location1 - doing a non-volatile read (which might be stale, but that does not change the correctness, as CompareExchange will check the value). If the current value (still) is what we had read from location1, CompareExchange will change the value to newValue. If not, we have to retry CompareExchange with the new current value, as returned by CompareExchange.

If the value is changed by another thread until the time of our next CompareExchange again, it will fail again, necessitating another retry - and this can in theory go on forever, hence the loop. Unless you are constantly changing the value from multiple threads, CompareExchange will most likely be called only once, if the current value is still what the non-volatile read of location1 yielded, or twice, if it was different.


Update 2022/8/17

As Dr. Strangelove and Theodor Zoulias pointed out in the comments, when location1 == Double.NaN, Add() would turn into an infinite loop.

So I had to change

if (newCurrentValue == currentValue)

to

if (newCurrentValue.Equals(currentValue))
冷清清 2024-08-11 11:10:02

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 位长整型的添加。

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

The Interlocked class wraps around the Windows API Interlocked** functions.

These are, in turn, wrapping around the native processor API, using the LOCK instruction prefix for x86. It only supports prefixing the following instructions:

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

You'll note that these, in turn, pretty much map to the interlocked methods. Unfortunately, the ADD functions for non-integer types are not supported here. Add for 64bit longs is supported on 64bit platforms.

Here's a great article discussing lock semantics on the instruction level.

腹黑女流氓 2024-08-11 11:10:02

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

但是,所有其他操作实际上都对目标的各个位进行操作,因此除非这些值实际上表示整数(或在某些情况下表示位数组),否则它们将不起作用。

As Reed Copsey has said, the Interlocked operations map (via Windows API functions) to instructions supported directly by the x86/x64 processors. Given that one of those functions is XCHG, you can do an atomic XCHG operation without really caring what the bits at the target location represent. In other words, the code can "pretend" that the 64-bit floating point number you are exchanging is in fact a 64-bit integer, and the XCHG instruction won't know the difference. Thus, .Net can provide Interlocked.Exchange functions for floats and doubles by "pretending" that they are integers and long integers, respectively.

However, all of the other operations actually do operate on the individual bits of the destination, and so they won't work unless the values actually represent integers (or bit arrays in some cases.)

吐个泡泡 2024-08-11 11:10:02

我怀疑有两个原因。

  1. .NET 的目标处理器仅支持整数类型的互锁增量。我相信这是 x86 上的 LOCK 前缀,其他处理器可能也存在类似的指令。
  2. 如果浮点数足够大,则向浮点数加一可能会得到相同的数字,因此我不确定是否可以将其称为增量。也许框架设计者试图避免这种情况下的非直观行为。

I suspect that there are two reasons.

  1. The processors targeted by .NET support interlocked increment only for integer types. I believe this is the LOCK prefix on x86, probably similar instructions exist for other processors.
  2. Adding one to a floating point number can result in the same number if it is big enough, so I'm not sure if you can call that an increment. Perhaps the framework designers are trying to avoid nonintuitive behavior in this case.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文