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

Использование Interlocked.CompareExchange с классом

Оператор

System.Threading.Interlocked.CompareExchange обеспечивает атомную (таким образом, поточно-безопасную) реализацию С# операции Compare-And-Swap.

Например int i = 5; Interlocked.CompareExchange(ref i, 10, 5); После этой команды int я будет иметь value = 10. А также сравнение и обмен происходит атомарно (одна операция).

Когда я попытался использовать это с экземпляром класса, сравнение сработает и значения не будут заменены.

   public class X
   {
       public int y;
       public X(int val) { y = val; }
   }

Теперь, когда я делаю

    X a = new X(1);
    X b = new X(1);
    X c = new X(2);
    Interlocked.CompareExchange<X>(ref a, c, b);

Операция сравнения и обмена не работает. Итак, я переопределил Equals и оператор == для класса X как

    public override bool Equals(object obj) { return y == ((X) obj).y; }

Итак, теперь я получаю Interlocked.Equals(a,b) как true, но операции CompareExchange все еще не работают.

Есть ли способ сделать это? Я хочу сравнить два экземпляра класса и назначить одно из них значение, основанное на сравнении.

4b9b3361

Ответ 1

Нет. Это невозможно сделать.

Interlocked.CompareExchange в основном сопоставляется непосредственно с инструкцией по сборке, которая способна атомарно сравнивать и заменять содержимое адреса памяти. Я считаю, что в 32-битном режиме доступна 64-разрядная версия инструкции (а также 32- и 16-разрядные версии), а в 64-битном режиме я думаю, что имеется 128-битная версия. Но это все. ЦП не имеет "swap-класс .NET", основанный на его специальной инструкции Equals.

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

Существует перегрузка функции Interlocked.CompareExchange, которая работает с объектными ссылками, но использует ссылочное равенство по вышеуказанной причине. Он просто сравнивает ссылки, а затем меняет их.

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

Ответ 2

Нормальное использование Interlocked.CompareExchange находится в шаблоне:

SomeType oldValue;
do
{
  oldValue = someField;
  someType newValue = [Computation based on oldValue]
} while (CompareExchange(ref someField, newValue, oldValue) != oldValue);

Основная идея заключается в том, что если поле не изменяется между временем чтения в oldValue и временем обработки CompareExchange, тогда newValue будет удерживать значение, которое должно быть сохранено в поле, Если что-то еще изменит его во время вычисления, результаты вычисления будут отменены, и вычисление будет повторяться с использованием нового значения. При условии, что вычисление выполняется быстро, чистый эффект заключается в том, чтобы позволить произвольному вычислению вести себя так, как если бы он был атомарным.

Если вы хотите выполнить операцию Compare-Exchange-style с использованием равенства Equals(), вы должны, вероятно, сделать что-то вроде:

SomeType newValue = desired new value;
SomeType compareValue = desired comparand;
SomeType oldValue;
do
{
  oldValue = someField;
  if (!oldValue.Equals(compareValue) return oldValue;
} while (CompareExchange(ref someField, newValue, oldValue) != oldValue);
return oldValue;

Обратите внимание, что если someField содержит ссылку на объект, который сравнивается с compareValue, а во время сравнения он будет изменен для хранения ссылки на другой объект, это новое значение будет проверено на compareValue, Процесс будет повторяться до тех пор, пока ни одно сравнение не сообщит, что значение, считанное из поля поля, не равно сравнению, или до тех пор, пока значение в поле не останется неизменным достаточно долго для завершения методов Equals() и CompareExchange.

Ответ 3

Я чувствую, что на этой странице есть некоторая путаница. Во-первых, комментатор прав, что вопрос содержит опасное предположение:

int i = 5; 
Interlocked.CompareExchange(ref i, 10, 5);

После этой команды int я будет иметь value = 10.

Нет, только если за это время значение i не изменилось на значение, отличное от 5. Хотя это кажется невероятным в показанном здесь коде, весь смысл использования CompareExchange заключается в том, что это должно быть возможно, поэтому здесь важна техническая составляющая. Я беспокоюсь, что OP может не понимать цель Interlocked.CompareExchange, особенно потому, что он не проверяет возвращаемое значение (см. Ниже).

Теперь текст исходного вопроса был:

"Есть ли способ сделать это? Я хочу сравнить два экземпляра класса и назначить одному из них значение на основе сравнения".

Поскольку для слова "это" не существует жизнеспособного предшественника, мы, возможно, должны рассмотреть в качестве вопроса здесь предложение, которое следует после, давая парафраз:

"Есть ли способ сравнить два экземпляра класса и присвоить одному из них значение, основанное на сравнении?"

К сожалению, этот вопрос до сих пор неясен или, возможно, имеет мало общего с атомными операциями. Во-первых, вы не можете "назначить [экземпляр класса] значение". Это просто не имеет смысла. Ссылка на экземпляр класса является значением, но нет никакого способа "присвоить" что-либо самому экземпляру класса. В этом большая разница по сравнению со значениями типов, которые можно назначать друг другу. Вы можете создать экземпляр, используя оператор new, но вы все равно просто получите ссылку на него. Опять же, это может показаться техническими деталями, но они являются критическими моментами, если вопрос действительно касается параллелизма без блокировок.

Далее, функция Interlocked.CompareExchange не обуславливает место хранения для какого-либо значения, а скорее сохраняет значение в (заданном) месте, что означает, что оно либо сохраняет значение (успех), либо оставляет место хранения без изменений ( отказ), при этом достоверно указывая, что из этого произошло.

Это означает, что фраза "на основе сравнения" не является полной о том, какими именно должны быть альтернативные действия. Глядя на предыдущую часть вопроса OP, можно предположить, что вопрос состоит в том, чтобы условно манипулировать ссылками на экземпляры, а атомарность - это красная сельдь. Это трудно понять, потому что, как отмечалось выше, CompareExchange (который использовался для постановки вопроса) не "подменяет" два значения в памяти, а только "сохраняет" одно значение.

X a = new X(1);
X b = new X(1);
X c = new X(2);

if (a.y == b.y)
    a = c;
else
    // ???

С перегрузкой Equals это можно упростить:

if (a == b)
    a = c;
else
    // ???

Фокусировка ОП на равенстве внутреннего поля y видимому, увеличивает вероятность того, что такое толкование вопроса находится на правильном пути. Но очевидно, что ответы в этом направлении не имеют ничего общего с Interlocked.CompareExchange. Нам нужно больше информации, чтобы понять, почему ФП считает, что назначение должно быть атомарным.

Таким образом, в качестве альтернативы, мы должны отметить, что также можно атомарно менять значения y в существующих экземплярах:

var Hmmmm = Interlocked.CompareExchange(ref a.y, c.y, b.y);

Или поменяйте местами ссылки на экземпляры, и теперь уже должно быть очевидно, что приравнивающие ссылки определяются только в терминах "равенство ссылок":

var Hmmmm = Interlocked.CompareExchange(ref a, c, b);

Чтобы перейти отсюда, вопрос должен быть более ясным. Например, чтобы переформулировать комментарий, сделанный в другом месте на этой странице, но более строго, было бы ошибкой не проверять возвращаемое значение Interlocked.CompareExchange.

Вот почему я сохранил возвращаемое значение в примере выше, и как я посчитал его имя подходящим. Не переходить на возвращаемое значение - значит не понимать базовых принципов параллельного параллельного доступа без блокировок, обсуждение которого выходит за рамки этого вопроса. Отличное введение см. В разделе " Параллельное программирование в Windows " Джо Даффи.

Наконец, я думаю, что весьма маловероятно, что OP действительно нужно атомарно хранить ссылки на классы, основываясь на произвольных соображениях, потому что это чрезвычайно специализированная операция, которая обычно необходима только в самом центре всеобъемлющей конструкции системы без блокировки. Но (вопреки другому ответу) это, безусловно, возможно в соответствии с тем, что описывает @supercat.

Поэтому, пожалуйста, не создавайте впечатление, что вы не можете писать код без блокировки в .NET или что ссылки на классы представляют собой какую-либо проблему для операций с Interlocked; на самом деле все наоборот: если вам нужно выполнить элементарную операцию, которая выбирает одно из двух разных мест хранения или иным образом влияет на несколько ячеек памяти, просто использовать конструкцию, в которой запутанные места оборачиваются в тривиальный содержащий класс, который затем дает вам единственную ссылку, которую можно атомарно поменять без блокировки. Кодирование без блокировок - простое занятие в .NET, так как оно менее хлопотно для объектов повторных попыток управления памятью в тех редких случаях, когда оптимистический путь терпит неудачу.

Достаточно сказать, что, по моему опыту, нет никакого существенного аспекта параллелизма без блокировок, которого я не смог бы достичь в С#/. NET/CLR, даже если он иногда немного грубоват, как вы могли бы установить из fooobar.com/info/432378/....