基于 CompareExchange 的互锁实现是否应该使用 SpinWait?

Should interlocked implementations based on CompareExchange use SpinWait?

下面是一个基于Interlocked.CompareExchange.

的联锁方法的实现

是否建议此代码在重复之前使用 SpinWait 自旋?

public static bool AddIfLessThan(ref int location, int value, int comparison)
{
    int currentValue;
    do
    {
        currentValue = location; // Read the current value
        if (currentValue >= comparison) return false; // If "less than comparison" is NOT satisfied, return false
    }
    // Set to currentValue+value, iff still on currentValue; reiterate if not assigned
    while (Interlocked.CompareExchange(ref location, currentValue + value, currentValue) != currentValue);
    return true; // Assigned, so return true
}

我见过 SpinWait 在这种情况下使用,但我的理论是它应该是不必要的。毕竟循环只有一小部分指令,总有一个线程在进步。

假设有两个线程竞相执行这个方法,第一个线程马上就成功了,而第二个线程一开始没有做任何改变,不得不重复。在没有其他竞争者的情况下,第二个线程 是否有可能在第二次尝试时失败

如果示例的第二个线程在第二次尝试时不会失败,那么我们可以从 SpinWait 中获得什么?在一百个线程竞相执行该方法的不太可能发生的情况下削减几个周期?

我的非专家意见是,在这种特殊情况下,两个线程偶尔调用 AddIfLessThan,不需要 SpinWait。如果两个线程都在紧密循环中调用 AddIfLessThan,这可能是有益的,这样每个线程都可以在一些微秒内不间断地取得进展。

实际上我做了一个实验并测量了一个线程在一个紧密循环中调用 AddIfLessThan 与两个线程的性能。这两个线程需要几乎四倍的时间才能进行相同数量的循环(累计)。添加 SpinWait 使两个线程只比单个线程慢一点。

两个线程不是 SpinWait 讨论的主题。但是这段代码并没有告诉我们实际上有多少线程可以竞争资源,并且线程数相对较多时使用 SpinWait 会变得有益。特别是随着线程数量的增加,试图成功获取资源的线程虚拟队列变得更长,而那些恰好在最后被服务的线程有很好的机会超过调度程序分配的时间片,而调度程序又可以导致更高的 CPU 消耗,并可能影响其他调度线程的执行,即使具有更高的优先级。 SpinWait 通过设置一些允许旋转的上限来很好地解决这种情况,在该上限之后将执行上下文切换。因此,在为了触发上下文切换而进行昂贵的系统调用的必要性和不受控制的用户模式 ​​CPU 消耗之间进行合理的权衡,后者在某些情况下有影响其他线程执行的风险。