Interlocked.CompareExchange <Int> с использованием GreaterThan или LessThan вместо равенства - программирование
Подтвердить что ты не робот

Interlocked.CompareExchange <Int> с использованием GreaterThan или LessThan вместо равенства

Объект System.Threading.Interlocked допускает добавление (вычитание) и сравнение как атомную операцию. Похоже, что CompareExchange, который просто не делает равенства, а также GreaterThan/LessThan как атомное сравнение, будет весьма ценным.

Будет ли гипотетическая Interlocked.GreaterThan функция ИЛ или это функция уровня ЦП? Оба?

Отсутствие какой-либо другой опции, возможно ли создать такую ​​функцию в С++ или прямом IL-коде и выставить эту функциональность для С#?

4b9b3361

Ответ 1

Обновление к более позднему сообщению, которое я сделал здесь: мы нашли лучший способ сделать большее сравнение с помощью дополнительного объекта блокировки. Мы написали много модульных тестов, чтобы проверить, что блокировка и блокировка могут использоваться вместе, но только для некоторых случаев.

Как работает код: Взаимосвязанный использует барьеры памяти, которые чтение или запись являются атомарными. Синхро-блокировка необходима для того, чтобы сделать большее, чем сравнение, действие атома. Таким образом, теперь правило состоит в том, что внутри этого класса никакая другая операция не записывает значение без этой блокировки синхронизации.

Что мы получаем с этим классом, это взаимосвязанное значение, которое можно прочитать очень быстро, но запись занимает немного больше. Читайте в нашем приложении примерно в 2-4 раза быстрее.

Здесь код в виде:

Смотрите здесь: http://files.thekieners.com/blogcontent/2012/ExchangeIfGreaterThan2.png

Здесь как код для копирования и вставки:

public sealed class InterlockedValue
{
    private long _myValue;
    private readonly object _syncObj = new object();

    public long ReadValue()
    {
        // reading of value (99.9% case in app) will not use lock-object, 
        // since this is too much overhead in our highly multithreaded app.
        return Interlocked.Read(ref _myValue);
    }

    public bool SetValueIfGreaterThan(long value)
    {
        // sync Exchange access to _myValue, since a secure greater-than comparisons is needed
        lock (_syncObj)
        {
            // greather than condition
            if (value > Interlocked.Read(ref  _myValue))
            {
                // now we can set value savely to _myValue.
                Interlocked.Exchange(ref _myValue, value);
                return true;
            }
            return false;
        }
    }
}

Ответ 3

Что вы думаете об этой реализации:

// this is a Interlocked.ExchangeIfGreaterThan implementation
private static void ExchangeIfGreaterThan(ref long location, long value)
{
    // read
    long current = Interlocked.Read(ref location);
    // compare
    while (current < value)
    {
        // set
        var previous = Interlocked.CompareExchange(ref location, value, current);
        // if another thread has set a greater value, we can break
        // or if previous value is current value, then no other thread has it changed in between
        if (previous == current || previous >= value) // note: most commmon case first
            break;
        // for all other cases, we need another run (read value, compare, set)
        current = Interlocked.Read(ref location);
    }
}

Ответ 4

Все блокированные операции имеют прямую поддержку в оборудовании.

Блокированные операции и тип атомных данных - это разные вещи. Атомный тип - это функция уровня библиотеки. На некоторых платформах и для некоторых типов данных атомизация реализована с использованием взаимосвязанных инструкций. В этом случае они очень эффективны.

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

Я не уверен, действительно ли Interlocked.GreaterThan. В противном случае он может быть уже реализован. Если вы знаете хороший пример, где это может быть полезно, я уверен, что все здесь будут рады услышать это.

Ответ 5

На самом деле это не так, но полезно придумать concurrency как входящий в 2 формы:

  • Блокировка concurrency
  • Блокировка concurrency

Это не так, потому что программная блокировка на основе concurrency заканчивается тем, что реализуется с помощью блокирующих ядерных инструкций где-то в стеке (часто в ядре). Однако, блокируйте бесплатные атомарные инструкции, все они в конечном итоге обретают аппаратную блокировку на шине памяти. Таким образом, на самом деле блокировка concurrency и блокировка на основе concurrency одинаковы.

Но концептуально, на уровне пользовательского приложения, это два разных способа делать вещи.

Основанный на блокировке concurrency основан на идее "блокировки" доступа к критическому разделу кода. Когда один поток "блокирует" критический раздел, ни один другой поток не может иметь код, выполняющийся внутри той же критической секции. Обычно это делается с помощью "мьютексов", которые взаимодействуют с планировщиком os и приводят к тому, что потоки становятся недействующими при ожидании входа в заблокированный критический раздел. Другой подход заключается в использовании "спиновых замков", которые заставляют нить вращаться в цикле, ничего полезного, пока критический раздел не станет доступен.

Lock free concurrency основан на идее использования атомных инструкций (особенно поддерживаемых процессором), которые гарантируются аппаратным обеспечением для атомарного запуска. Interlocked.Increment - хороший пример блокировки concurrency. Он просто вызывает специальные инструкции процессора, которые выполняют атомный приращение.

Заблокировать concurrency сложно. Это особенно усложняется по мере увеличения длины и сложности критических секций. Любой шаг в критическом разделе может быть одновременно выполнен любым количеством потоков одновременно, и они могут двигаться с совершенно разными скоростями. Вы должны убедиться, что, несмотря на это, результаты системы в целом остаются правильными. Для чего-то вроде приращения, это может быть просто (cs - всего лишь одна инструкция). Для более сложных критических разделов ситуация может очень быстро усложниться.

Блокировка на основе concurrency также сложна, но не так сложна, как блокировка concurrency. Это позволяет создавать произвольно сложные области кода и знать, что в любой момент выполняется только один поток.

Блокировка concurrency имеет одно большое преимущество, однако: скорость. При правильном использовании он может быть на порядок быстрее, чем блокировка concurrency. Спиновые петли плохи для длинных критических секций, потому что они тратят ресурсы ЦП, ничего не делая. Мьютексы могут быть плохими для небольших критических разделов, потому что они вводят много накладных расходов. Они включают в себя переключатель режима как минимум, а несколько контекстных переключателей в худшем случае.

Рассмотрите возможность реализации Управляемой кучи. Вызов в ОС каждый раз, когда вызывается "новое", будет ужасным. Это разрушит производительность вашего приложения. Однако, используя блокировку free concurrency, можно реализовать распределение памяти 0 из 0 с использованием блокированного приращения (я точно не знаю, что делает CLR, но я был бы удивлен, если бы это было не так. ОГРОМНАЯ экономия.

Существуют и другие виды использования, например, в структурах данных, свободных от блокировки, таких как постоянные стеки и деревья avl. Обычно они используют "cas" (сравнить и обменивать).

Однако причина, по которой блокировка на основе concurrency и блокировка concurrency действительно эквивалентна, объясняется деталями реализации каждого из них.

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

Атомные инструкции, в свою очередь, реализованы с помощью аппаратных блокировок.

В любом случае, у них обоих есть свои компромиссы, обычно сосредоточенные на персидском и сложном уровнях. Мьютекс может быть как быстрее, так и медленнее, чем код блокировки. Блокировка свободного кода может быть как более и менее сложной, чем мьютексом. Соответствующий механизм использования зависит от конкретных обстоятельств.

Теперь, чтобы ответить на ваш вопрос:

Метод, который выполнял взаимный обмен обмена, если он меньше, чем для вызывающих, что он не использует блокировки. Вы не можете реализовать его с помощью одной команды так же, как приращение или сравнение обмена. Вы могли бы имитировать его, делая вычитание (для вычисления меньше), с блокировкой сравнения в цикле. Вы также можете сделать это с помощью мьютекса (но это будет означать блокировку, и поэтому использование "блокировки" в имени будет вводить в заблуждение). Уместно ли построить версию "имитированная взаимосвязь через cas"? Это зависит. Если код вызывается очень часто и имеет очень небольшое противоречие с потоком, тогда ответ да. Если нет, вы можете превратить операцию O (1) с умеренно высокими постоянными факторами в бесконечный (или очень длинный) цикл, и в этом случае было бы лучше использовать мьютекс.

В большинстве случаев это не стоит.

Ответ 6

С помощью этих вспомогательных методов вы можете не только обменивать стоимость, но и обнаруживать, было ли это заменено или нет.

Использование выглядит следующим образом:

int currentMin = 10; // can be changed from other thread at any moment

int potentialNewMin = 8;
if (InterlockedExtension.AssignIfNewValueSmaller(ref currentMin, potentialNewMin))
{
    Console.WriteLine("New minimum: " + potentialNewMin);
}

И вот методы:

public static class InterlockedExtension
{
    public static bool AssignIfNewValueSmaller(ref int target, int newValue)
    {
        int snapshot;
        bool stillLess;
        do
        {
            snapshot = target;
            stillLess = newValue < snapshot;
        } while (stillLess && Interlocked.CompareExchange(ref target, newValue, snapshot) != snapshot);

        return stillLess;
    }

    public static bool AssignIfNewValueBigger(ref int target, int newValue)
    {
        int snapshot;
        bool stillMore;
        do
        {
            snapshot = target;
            stillMore = newValue > snapshot;
        } while (stillMore && Interlocked.CompareExchange(ref target, newValue, snapshot) != snapshot);

        return stillMore;
    }
}

Ответ 7

Большие/Меньше и равные - уже являются атомарными операциями. Это не касается безопасного одновременного поведения вашего приложения.

Нет смысла делать их частью семьи с блокировкой, поэтому возникает вопрос: чего вы на самом деле пытаетесь достичь?