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

Оператор System.Threading.Interlocked.CompareExchange обеспечивает атомарную (таким образом потокобезопасную) реализацию на C # операции сравнения и замены.

Например, int i = 5; Interlocked.CompareExchange(ref i, 10, 5);. После этой команды int i будет иметь значение = 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 по-прежнему не работают.

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


person Bhargav Mangipudi    schedule 14.07.2011    source источник
comment
Я предполагаю, что CompareExchange использует ReferenceEquals для сравнения a и b, что имеет смысл. Ваше переопределение Equals рекомендуется только для неизменяемых типов. Вы тщательно продумали, какое поведение Equals вам нужно / нужно?   -  person Henk Holterman    schedule 14.07.2011
comment
К сожалению, не задокументировано, как в этом контексте определяется равенство.   -  person Daniel Hilgarth    schedule 14.07.2011
comment
@Henk Предположим, что мне нужно поведение, как указано выше, когда два экземпляра класса равны, когда их член y имеет одинаковое значение.   -  person Bhargav Mangipudi    schedule 14.07.2011
comment
Как поясняет @jaff ниже, это невозможно сделать атомарным.   -  person Henk Holterman    schedule 14.07.2011
comment
Ваш пример сломан. После этой команды int i будет иметь значение = 10 - только если значение, возвращаемое CompareExchange, равно 5 (или 10 :-)). Возможно, это был упрощенный пример кода, но я видел много неработающего кода, когда люди забывают, что вам нужно проверить, что CompareExchange был успешным, прежде чем продолжить.   -  person Damien_The_Unbeliever    schedule 14.07.2011


Ответы (3)


Нет, это невозможно.

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

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

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

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

person jalf    schedule 14.07.2011
comment
Спасибо. Использование блокировок сводит на нет цель использования атомарных операций по разным причинам, но, похоже, у меня нет особого выбора. Кроме того, мне было интересно, что операция поддерживает замену struct, поскольку они не являются ссылочными типами. - person Bhargav Mangipudi; 14.07.2011
comment
@Bhargav: нет, как описано в документации по этому методу.: этот метод поддерживает только ссылочные типы. - person Daniel Hilgarth; 14.07.2011
comment
@Bhargav: структуры не лучше. Я добавил несколько подробностей к своему ответу, чтобы, надеюсь, объяснить его. ЦП просто слепо сравнивает две ссылки (которые в основном являются указателями или адресами памяти) и меняет их местами. Он ничего не знает о том, на что указывают ориентиры на, и не знает, что вы определили особые способы сравнения этих объектов. Он видит две ссылки как непрозрачные блоки данных и проверяет, хранят ли они один и тот же битовый шаблон (они указывают на один и тот же объект). - person jalf; 14.07.2011

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

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

После этой команды int i будет иметь значение = 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. Нам потребуется дополнительная информация, чтобы узнать, почему OP считает, что присвоение должно быть атомарным.

В качестве альтернативы мы должны отметить, что также можно атомарно поменять местами 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 операций; на самом деле все наоборот: если вам действительно нужно выполнить атомарную операцию, которая выбирает между двумя разными местами хранения или иным образом влияет на несколько мест памяти, просто использовать дизайн, в котором запутанные места обертываются тривиальным, содержащим class, который затем дает вам единственную ссылку, которую можно атомарно поменять местами без блокировки. Безблокировочное кодирование в .NET - легкий ветерок, поскольку в редких случаях, когда оптимистичный путь не работает, с ним легче справляются объекты повторных попыток с управлением памятью.

Достаточно сказать, что, по моему опыту, нет существенного аспекта параллелизма без блокировок, которого я не смог бы достичь в C # /. NET / CLR, даже если это иногда немного грубо. по краям, как вы могли убедиться из https://stackoverflow.com/a/5589515/147511.

person Glenn Slayden    schedule 01.02.2017

Обычное использование 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 с использованием равенства 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.

person supercat    schedule 11.02.2013