Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Concurrent modification of double[][] elements without locking

I have a jagged double[][] array that may be modified concurrently by multiple threads. I should like to make it thread-safe, but if possible, without locks. The threads may well target the same element in the array, that is why the whole problem arises. I have found code to increment double values atomically using the Interlocked.CompareExchange method: Why is there no overload of Interlocked.Add that accepts Doubles as parameters?

My question is: will it stay atomic if there is a jagged array reference in Interlocked.CompareExchange? Your insights are much appreciated.

With an example:

    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();
            }
        }
    }
like image 250
tethered.sun Avatar asked Jan 31 '17 10:01

tethered.sun


2 Answers

Yes, it will still be atomic and thread-safe. Any calls to the same cell will be passing the same address-to-a-double. Details like whether it is in an array of as a field on an object are irrelevant.

However, the line:

double newCurrentValue = items[i][j];

is not atomic - that could in theory give a torn value (especially on x86). That's actually OK in this case because in the torn-value scenario it will just hit the loop, count as a collision, and redo - this time using the known-atomic value from CompareExchange.

like image 184
Marc Gravell Avatar answered Oct 27 '22 11:10

Marc Gravell


Seems that you want eventually add some value to an array item.

I suppose there are only updates of values (array itself stays the same piece of memory) and all updates are done via this AddToItem method.
So, you have to read updated value each time (otherwise you lost changes done by other thread, or get infinite loop).

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();
        }
    }
}


Note that we have to use some volatile method to read items[i][j]. Volatile.Read is used to avoid some unwanted optimizations that are allowed under .NET Memory Model (see ECMA-334 and ECMA-335 specifications).
Since we update value atomically (via Interlocked.CompareExchange) it's enough to read items[i][j] via Volatile.Read.

If not all changes to this array are done in this method, then it's better to write a loop in which create local copy of array, modify it and update reference to new array (using Volatile.Read and Interlocked.CompareExchange)

like image 45
Valery Petrov Avatar answered Oct 27 '22 11:10

Valery Petrov