Есть ли способ заменить этот код с помощью Interlocked.Exchange
API?
if (IsWorking == false)
{
lock (this)
{
if (IsWorking == false)
{
IsWorking = true;
}
}
}
Есть ли способ заменить этот код с помощью Interlocked.Exchange
API?
if (IsWorking == false)
{
lock (this)
{
if (IsWorking == false)
{
IsWorking = true;
}
}
}
Да, но будет ли это полезно, это другой вопрос.
Вы можете заменить 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
вокруг доступа к словарю, когда конкуренция низкая, но намного лучше, когда к нему одновременно обращаются многие потоки.
Обычно для этого используется 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, независимо от того, или не происходит обмен».
Прежде всего, 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, вы знаете, что не меняли состояние.
this
. - person PoweredByOrange   schedule 25.11.2014