Почему нет перегрузки Interlocked.Add, который принимает Doubles в качестве параметров?
Я полностью понимаю атомарность, которую предоставляет класс Threading.Interlocked; Однако я не понимаю, почему функция Add предлагает только две перегрузки: одну для целых, другую для Longs. Почему бы не удваивать или какой-либо другой числовой тип в этом отношении?
Очевидно, что предполагаемый метод изменения Double - CompareExchange; Я УГАДАЮ это, потому что изменение Double - более сложная операция, чем изменение целого. Тем не менее мне непонятно, почему, если CompareExchange и Add могут принимать целые числа, они не могут также принимать парные числа.
Ответы
Ответ 1
Взаимосвязанный класс обертывает функции Windows с блокировкой **.
Они, в свою очередь, обертывают собственный API-интерфейс процессора, используя префикс инструкции LOCK для x86. Он поддерживает только префиксы следующих инструкций:
BT, BTS, BTR, BTC, XCHG, XADD, ADD, OR, ADC, SBB, AND, SUB, XOR, NOT, NEG, INC, DEC
Вы заметите, что они, в свою очередь, в значительной степени сопоставляются взаимосвязанным методам. К сожалению, функции ADD для нецелочисленных типов здесь не поддерживаются. Добавление для 64-битных длин поддерживается на 64-битных платформах.
Здесь отличная статья обсуждение семантики блокировки на уровне инструкций.
Ответ 2
Другие обратились к "почему?". Однако легко свернуть собственный 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
дало или два раза, если оно было другим.
Ответ 3
Как сказал Рид Копси, карта взаимосвязанных операций (через функции Windows API) с инструкциями, поддерживаемыми непосредственно процессорами x86/x64. Учитывая, что одной из этих функций является XCHG, вы можете выполнять атомную операцию XCHG, не заботясь о том, что представляют собой биты в целевом местоположении. Другими словами, код может "притворяться", что 64-разрядный номер с плавающей запятой, который вы обмениваете, на самом деле является 64-битным целым числом, и инструкция XCHG не будет знать разницу. Таким образом,.Net может предоставлять функции Interlocked.Exchange для float и double, "делая вид", что они являются целыми и длинными целыми числами соответственно.
Однако все другие операции фактически работают с отдельными битами адресата, и поэтому они не будут работать, если значения фактически не представляют целые числа (или бит-массивы в некоторых случаях.)
Ответ 4
Я подозреваю, что есть две причины.
- Процессоры, ориентированные на .Net, поддерживают блокировку приращения только для целых типов. Я считаю, что это префикс LOCK на x86, вероятно, подобные инструкции существуют для других процессоров.
- Добавление одного из числа с плавающей запятой может привести к тому же числу, если оно достаточно велико, поэтому я не уверен, что вы можете назвать это приращением. Возможно, разработчики фреймворка стараются избежать неинтуитивного поведения в этом случае.
Ответ 5
Как отметил Адам Робинсон, есть перегрузка для Interlocked.Increment
, которая принимает Int64, но обратите внимание:
Метод чтения и 64-разрядный перегрузки Приращения, Уменьшения, и методы Add действительно являются атомными на системах, где System.IntPtr имеет длину 64 бит. В других системах, эти методы являются атомными с учетом друг другу, но не в отношении другие средства доступа к данным. Таким образом, чтобы быть потокобезопасным на 32-битном систем, любой доступ к 64-битовому значению должны осуществляться через членов класс блокировки.