并发修改 double[][] 元素而不加锁

Concurrent modification of double[][] elements without locking

我有一个锯齿状的 double[][] 数组,可以由多个线程同时修改。我想让它成为线程安全的,但如果可能的话,没有锁。线程很可能以数组中的相同元素为目标,这就是出现整个问题的原因。我找到了使用 Interlocked.CompareExchange 方法自动增加双精度值的代码:Why is there no overload of Interlocked.Add that accepts Doubles as parameters?

我的问题是:如果 Interlocked.CompareExchange 中有参差不齐的数组引用,它会保持原子性吗?非常感谢您的见解。

举个例子:

    public class Example
    {
        double[][] items;

        public void AddToItem(int i, int j, double addendum)
        {
            double newCurrentValue = items[i][j];
            double currentValue;
            double newValue;
            SpinWait spin = new SpinWait();

            while (true) {
               currentValue = newCurrentValue;
               newValue = currentValue + addendum;
               // This is the step of which I am uncertain:
               newCurrentValue = Interlocked.CompareExchange(ref items[i][j], newValue, currentValue);
               if (newCurrentValue == currentValue) break;
               spin.SpinOnce();
            }
        }
    }

是的,它仍然是原子的并且 thread-safe。对同一单元格的任何调用都将传递相同的 address-to-a-double。诸如它是否在对象的字段数组中的详细信息是无关紧要的。

但是,行:

double newCurrentValue = items[i][j];

不是原子的 - 理论上 可以给出一个撕裂的值(尤其是在 x86 上)。在这种情况下,这实际上没问题,因为在 torn-value 场景中,它只会进入循环,算作碰撞,然后重做 - 这次使用 CompareExchange.[=12 中的 known-atomic 值=]

您似乎希望最终向数组项添加一些值。

我想只有值的更新(数组本身保持同一块内存)并且所有更新都是通过这个 AddToItem 方法完成的。
所以,你每次都必须读取更新的值(否则你会丢失其他线程所做的更改,或者得到无限循环)。

public class Example
{
    double[][] items;

    public void AddToItem(int i, int j, double addendum)
    {
        var spin = new SpinWait();

        while (true)
        {
            var valueAtStart = Volatile.Read(ref items[i][j]);
            var newValue = valueAtStart + addendum;

            var oldValue = Interlocked.CompareExchange(ref items[i][j], newValue, valueAtStart);

            if (oldValue.Equals(valueAtStart))
                break;
            spin.SpinOnce();
        }
    }
}


请注意,我们必须使用一些 volatile 方法来读取 items[i][j]Volatile.Read 用于避免 .NET 内存模型允许的一些不必要的优化(请参阅 ECMA-334 和 ECMA-335 规范)。
由于我们以原子方式更新值(通过 Interlocked.CompareExchange),因此通过 Volatile.Read.

读取 items[i][j] 就足够了 如果不是所有对该数组的更改都在此方法中完成,那么最好编写一个循环,在其中创建数组的本地副本,修改它并更新对新数组的引用(使用 Volatile.ReadInterlocked.CompareExchange