Замена блокировки заблокированной операцией

Есть ли способ заменить этот код с помощью Interlocked.Exchange API?

if (IsWorking == false)
{
    lock (this)
    {
        if (IsWorking == false)
        {
            IsWorking = true;
        }
    }
}

person crazy novice    schedule 25.11.2014    source источник
comment
Почему у вас есть блокировка с двойной проверкой для назначения логического значения?   -  person Sam Greenhalgh    schedule 25.11.2014
comment
Такое логическое значение почти всегда должно быть Auto/ManualResetEvent. Что избавляет от блокировки.   -  person Hans Passant    schedule 25.11.2014
comment
В качестве примечания, обычно ПЛОХАЯ идея блокировать this.   -  person PoweredByOrange    schedule 25.11.2014


Ответы (3)


Да, но будет ли это полезно, это другой вопрос.

Вы можете заменить isWorking целым числом, а затем использовать Interlocked.CompareExchange(ref isWorking, 1, 0).

Это бессмысленно; в любом случае результат в конце таков, что isWorking равен 1, поэтому у нас будет лучший параллелизм, просто заменив код на IsWorking = true (или, возможно, на VolatileWrite, чтобы убедиться, что его видят другие процессоры).

Ваш код, вероятно, представляет собой сокращение чего-то вроде:

if (isWorking == false)
    lock (this)
        if (isWorking == false)
        {
            DoSomethingWorthDoing();
            isWorking = true;
        }

И падает часть DoSomethingWorthDoing().

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

if(someUsefulThing == null)
  Interlocked.CompareExchange(ref someUsefulThing, SomeUsefulFactory(), null);

В конце этого someUsefulThing будет установлен результат SomeUsefulFactory(), и после установки он больше не будет установлен. Однако будет период, в течение которого мы можем вызывать SomeUsefulFactory() несколько раз только для того, чтобы отбросить результат. Иногда это катастрофа, иногда все в порядке, а иногда мы могли бы просто не делать блокировки и все было бы в порядке; это зависит от того, почему мы заинтересованы в совместном использовании одного и того же объекта здесь.

Есть и другие варианты, но применимость зависит только от того, почему вы заботитесь о параллелизме. Этот код, например, реализует потокобезопасный параллельный словарь, использующий такие взаимосвязанные операции, что, как правило, медленнее, чем просто поставить lock вокруг доступа к словарю, когда конкуренция низкая, но намного лучше, когда к нему одновременно обращаются многие потоки.

person Jon Hanna    schedule 25.11.2014

Обычно для этого используется Interlocked.CompareExchange. Но, к сожалению, нет перегрузки, которая принимает логическое значение, а перегрузки object и generic работают только со ссылочными типами. Однако вы можете взломать это и использовать int только с двумя значениями (0 и 1):

private static int IsWorking = 0;
private static void Main()
{
    var originalValue = Interlocked.CompareExchange(ref IsWorking, 1, 0);
}

Interlocked.CompareExchange, как следует из названия, сравнивает 2 значения (IsWorking и 0) и, если они равны, сохраняет другое значение (1) в исходном месте. Возвращаемое значение — это то, что было в исходном местоположении до вызова этого атомарного метода. Таким образом, если был возвращен 0, вызов заменил значение в IsWorking, а если это 1, то первым туда попал другой поток.

О Interlocked.CompareExchange:

«Если сравнение и значение в location1 равны, то значение сохраняется в location1. В противном случае операция не выполняется. Операции сравнения и обмена выполняются как атомарная операция. Возвращаемое значение CompareExchange — это исходное значение в location1, независимо от того, или не происходит обмен».

person i3arnon    schedule 25.11.2014

Прежде всего, IsWorking = true будет эквивалентно, если нет другого параллельного кода, который удерживает блокировку при выполнении. Такие присваивания примитивных значений гарантированно атомарны. Ясно, что комбинация условного оператора и присваивания не может быть атомарной. Однако похоже, что код хочет установить для IsWorking значение true, только если IsWorking теперь имеет значение false. Однако, если IsWorking теперь имеет значение true, что может быть плохого в том, чтобы переустановить его на True? Похоже, вам не хватает чего-то, что требует потокобезопасного способа уведомить внешний мир об изменении состояния. Здесь вы можете использовать событие или монитор.

Вы также можете искать Interlocked.CompareExchange, но это будет работать только для ссылочных типов и примитивных числовых типов. Таким образом, вам нужно будет перейти, например, с логического на int. Однако метод CompareExchange не будет ждать, пока будет ждать ваша блокировка. Он просто вернет старое значение независимо от того, было оно заменено или нет.

Итак, если вы изменили IsWorking с логического свойства на поле int, вы могли бы:

int wasWorking = Interlocked.CompareExchange(ref isWorking, 1, 0);

если wasWorking равно 0, вы знаете, что изменили состояние, если wasWorking равно 1, вы знаете, что не меняли состояние.

person Mike Schenk    schedule 25.11.2014