Почему нет перегрузки Interlocked.Add, которая принимает Doubles в качестве параметров? - PullRequest
20 голосов
/ 09 сентября 2009

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

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

Ответы [ 4 ]

31 голосов
/ 03 июня 2013

Другие обратились к «почему?». Однако легко создать свой собственный 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, или дважды, если оно было другим.

25 голосов
/ 09 сентября 2009

Класс Interlocked охватывает функции Windows API Interlocked **.

Это, в свою очередь, обертывание вокруг API собственного процессора с использованием префикса инструкции LOCK для x86. Он поддерживает только префикс следующих инструкций:

BT, BTS, BTR, BTC, XCHG, XADD, ADD, OR, ADC, SBB, AND, SUB, XOR, NOT, NEG, INC, DEC

Вы заметите, что они, в свою очередь, в значительной степени соответствуют взаимосвязанным методам. К сожалению, функции ADD для нецелых типов здесь не поддерживаются. Добавление для 64-битных длин поддерживается на 64-битных платформах.

Вот отличная статья , обсуждающая семантику блокировки на уровне команд .

7 голосов
/ 28 июня 2011

Как сказал Рид Копси, блокированные операции отображаются (с помощью функций Windows API) на инструкции, поддерживаемые непосредственно процессорами x86 / x64. Учитывая, что одной из этих функций является XCHG, вы можете выполнить атомарную операцию XCHG, не заботясь о том, что представляют собой биты в целевом местоположении. Другими словами, код может «притвориться», что 64-разрядное число с плавающей запятой, которым вы обмениваетесь, на самом деле является 64-разрядным целым числом, и инструкция XCHG не будет знать разницу. Таким образом, .Net может предоставлять функции Interlocked.Exchange для чисел с плавающей запятой и двойников, «притворяясь», что они являются целыми и длинными целыми числами соответственно.

Однако все остальные операции на самом деле работают с отдельными битами адресата, и поэтому они не будут работать, если значения на самом деле не представляют собой целые числа (или битовые массивы в некоторых случаях).

1 голос
/ 09 сентября 2009

Я подозреваю, что есть две причины.

  1. Процессоры, предназначенные для .Net, поддерживают инкремент с блокировкой только для целочисленных типов. Я считаю, что это префикс LOCK на x86, вероятно, аналогичные инструкции существуют для других процессоров.
  2. Добавление одного к числу с плавающей запятой может привести к тому же самому числу, если оно достаточно большое, поэтому я не уверен, что вы можете назвать это приращением. Возможно, разработчики фреймворка пытаются избежать неинтуитивного поведения в этом случае.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...