1

可能重复:
Interlocked.CompareExchange<Int> 使用 GreaterThan 或 LessThan 而不是相等

我知道 Interlocked.CompareExchange 仅在值和比较值相等时交换值,
如果它们不相等如何交换它们以实现这样的目标?

if (Interlocked.CompareExchange(ref count, count + 1, max) != max)
    // i want it to increment as long as it is not equal to max
        {
           //count should equal to count + 1
        }
4

2 回答 2

6

Marc 发布的更高效(更少的总线锁定和更少的读取)和简化的实现:

static int InterlockedIncrementAndClamp(ref int count, int max)
{
    int oldval = Volatile.Read(ref count), val = ~oldval;

    while(oldval != max && oldval != val)
    {   
        val = oldval;
        oldval = Interlocked.CompareExchange(ref count, oldval + 1, oldval);
    }

    return oldval + 1;
}

如果您的争用非常高,我们可以通过将常见情况减少为单个原子增量指令来进一步提高可伸缩性:与 CompareExchange 的开销相同,但不会出现循环。

static int InterlockedIncrementAndClamp(ref int count, int max, int drift)
{
    int v = Interlocked.Increment(ref count);

    while(v > (max + drift))
    {
        // try to adjust value.
        v = Interlocked.CompareExchange(ref count, max, v);
    }

    return Math.Min(v, max);
}

count在这里,我们允许drift超过max. 但我们仍然只返回max. 这允许我们在大多数情况下将整个操作折叠成单个原子增量,这将允许最大的可扩展性。如果我们的价值超过我们的drift价值,我们只需要一个以上的操作,您可能可以将其做得足够大以使其非常稀有。

为了回应 Marc 对 Interlocked 和 non-Interlocked 内存访问一起工作的担忧:

关于具体volatilevs Interlocked:volatile只是一个普通的内存操作,但一个没有优化掉,一个没有相对于其他内存操作重新排序。这个特定问题并不围绕这些特定属性中的任何一个,所以我们实际上是在谈论非互锁与互锁互操作性。

.NET 内存模型保证基本整数类型的读取和写入(直到机器的本机字大小),并且引用是原子的。Interlocked 方法也是原子的。因为 .NET 只有一个“原子”定义,所以它们不需要明确的特殊情况说明它们相互兼容。

有一件事Volatile.Read不能保证可见性:您将始终获得加载指令,但 CPU 可能会从其本地缓存中读取旧值,而不是由不同 CPU 放入内存中的新值。x86 在大多数情况下不需要担心这一点(特殊指令,例如MOVNTPS例外),但对于其他架构来说这是非常可能的事情。

总而言之,这描述了两个可能影响 的问题Volatile.Read:首先,我们可能在 16 位 CPU 上运行,在这种情况下读取 anint不会是原子的,我们读取的可能不是其他人正在写入的值。其次,即使它是原子的,由于可见性,我们可能正在读取一个旧值。

但是影响Volatile.Read并不意味着它们会影响整个算法,这是完全安全的。

如果您以非原子方式同时写入,第一种情况只会咬我们。count这是因为最终可能发生的是(写 A[0];CAS A[0:1];写 A[1])。因为我们所有的写入都count发生在保证原子的 CAS 中,所以这不是问题。当我们只是在阅读时,如果我们读取了错误的值,它将被即将到来的 CAS 捕获。

如果你仔细想想,第二种情况实际上只是普通情况的一种特殊情况,即读取和写入之间的值发生变化——读取发生在我们请求它之前。在这种情况下,第一次Interlocked.CompareExchange调用将报告与给出的值不同的值Volatile.Read,并且您将开始循环直到它成功。

如果您愿意,可以将Volatile.Read视为针对低争用情况的纯粹优化。oldval我们可以使用它进行初始化,0它仍然可以正常工作。使用Volatile.Read它很有可能只执行一个 CAS(按照说明,这非常昂贵,尤其是在多 CPU 配置中)而不是两个。

但是,是的,正如 Marc 所说——有时锁更简单!

于 2012-11-25T23:46:50.570 回答
4

但是,没有“如果不相等则进行比较”:您可以先自己测试该值,然后仅在没有线程竞赛时才进行更新;这通常意味着如果第二次测试失败,您可能需要循环。在伪代码中:

bool retry;
do {
    retry = false;
    // get current value
    var val = Interlocked.CompareExchange(ref field, 0, 0);
    if(val != max) { // if not maxed
        // increment; if the value isn't what it was above: redo from start
        retry = Interlocked.CompareExchange(ref field, val + 1, val) != val;
    }        
} while (retry);

但坦率地说,锁会更简单。

于 2012-11-25T23:28:11.747 回答