Interlocked.CompareExchange如果不相等?

use*_*906 1 c# multithreading atomic interlocked

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

我知道Interlocked.CompareExchange仅在值和comparand相等时交换值,
如果不等于实现这样的话,如何交换它们?

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
        }
Run Code Online (Sandbox Code Playgroud)

Cor*_*son 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;
}
Run Code Online (Sandbox Code Playgroud)

如果您有很高的争用,我们可以通过将常见情况减少到单个原子增量指令来进一步提高可伸缩性:与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);
}
Run Code Online (Sandbox Code Playgroud)

在这里,我们允许count上到drift超越值max.但我们仍然只能回归max.这允许我们在大多数情况下将整个op折叠成单个原子增量,这将允许最大的可伸缩性.如果我们超过我们的drift价值,我们只需要不止一个操作,你可能做得足够大,非常罕见.

回应Marc对互锁和非互锁内存访问的担忧:

特别是volatile对于Interlocked来说:volatile只是一个普通的内存操作,但是没有被优化掉,而且没有对其他内存操作进行重新排序.这个具体问题不是围绕这两个特定属性,所以我们真正谈论的是非互锁与互锁的互操作性.

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

有一件事Volatile.Read不能保证是知名度:您总能获得一个加载指令,但CPU可能读取其本地缓存,而不是仅仅把内存由不同CPU的新值的旧值.在大多数情况下,x86不需要担心这种情况(特殊指令,例如MOVNTPS异常),但是对于其他架构来说这是非常可能的事情.

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

但是影响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所说 - 有时锁更简单!