Подтвердить что ты не робот

Почему нет перегрузки Interlocked.Add, который принимает Doubles в качестве параметров?

Я полностью понимаю атомарность, которую предоставляет класс Threading.Interlocked; Однако я не понимаю, почему функция Add предлагает только две перегрузки: одну для целых, другую для Longs. Почему бы не удваивать или какой-либо другой числовой тип в этом отношении?

Очевидно, что предполагаемый метод изменения Double - CompareExchange; Я УГАДАЮ это, потому что изменение Double - более сложная операция, чем изменение целого. Тем не менее мне непонятно, почему, если CompareExchange и Add могут принимать целые числа, они не могут также принимать парные числа.

4b9b3361

Ответ 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-битовому значению должны осуществляться через членов класс блокировки.