Другие обратились к «почему?». Однако легко создать свой собственный Add(ref double, double)
, используя примитив CompareExchange
:
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 == currentValue)
return newValue;
}
}
CompareExchange
устанавливает значение location1
равным newValue
, если текущее значение равно currentValue
. Поскольку он делает это атомарно, потокобезопасным способом, мы можем полагаться только на него, не прибегая к блокировкам.
Почему цикл while (true)
? Подобные циклы являются стандартными при реализации оптимистично параллельных алгоритмов. CompareExchange
не изменится location1
, если текущее значение отличается от currentValue
. Я инициализировал currentValue
на location1
- выполнял энергонезависимое чтение (что может быть устаревшим, но это не меняет правильность, так как CompareExchange
проверит значение). Если текущее значение (все еще) - это то, что мы прочитали из location
, CompareExchange
изменит значение на newValue
. Если нет, мы должны повторить попытку CompareExchange
с новым текущим значением, которое возвращает CompareExchange
.
Если значение будет изменено другим потоком до момента следующего следующего CompareExchange
, оно снова потерпит неудачу, что потребует повторной попытки - и теоретически это может продолжаться вечно, следовательно, цикл. Если вы постоянно не меняете значение из нескольких потоков, CompareExchange
, скорее всего, будет вызываться только один раз, если текущее значение все еще равно, что дало энергонезависимое чтение location1
, или дважды, если оно было другим.