Others have addressed the "why?". It is easy however to roll your own Add(ref double, double)
, using the CompareExchange
primitive:
public static double Add(ref double location1, double value)
{
double newCurrentValue = location1; // non-volatile read, so may be stale
while (true)
{
double currentValue = newCurrentValue;
double newValue = currentValue + value;
newCurrentValue = Interlocked.CompareExchange(ref location1, newValue, currentValue);
if (newCurrentValue.Equals(currentValue)) // see "Update" below
return newValue;
}
}
CompareExchange
sets the value of location1
to be newValue
, if the current value equals currentValue
. As it does so in an atomic, thread-safe way, we can rely on it alone without resorting to locks.
Why the while (true)
loop? Loops like this are standard when implementing optimistically concurrent algorithms. CompareExchange
will not change location1
if the current value is different from currentValue
. I initialized currentValue
to location1
- doing a non-volatile read (which might be stale, but that does not change the correctness, as CompareExchange
will check the value). If the current value (still) is what we had read from location1
, CompareExchange
will change the value to newValue
. If not, we have to retry CompareExchange
with the new current value, as returned by CompareExchange
.
If the value is changed by another thread until the time of our next CompareExchange
again, it will fail again, necessitating another retry - and this can in theory go on forever, hence the loop. Unless you are constantly changing the value from multiple threads, CompareExchange
will most likely be called only once, if the current value is still what the non-volatile read of location1
yielded, or twice, if it was different.
Update 2022/8/17
As Dr. Strangelove and Theodor Zoulias pointed out in the comments, when location1 == Double.NaN
, Add()
would turn into an infinite loop.
So I had to change
if (newCurrentValue == currentValue)
to
if (newCurrentValue.Equals(currentValue))