Используя Блокировать.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) as true, а CompareExchange операции по-прежнему не удается.

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

3 ответов


нет. Это невозможно.

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

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

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

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


нормальное использование Interlocked.CompareExchange в шаблоне:

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

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

если вы хотите выполнить операцию сравнения в стиле обмена, используя 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. Процесс будет повторяться до тех пор, пока сравнение не сообщит, что значение, считанное из поля field, не было равно comparand, или пока значение в поле не останется неизменным достаточно долго для обоих Equals() и CompareExchange методы, чтобы закончить.


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

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

после этого int у меня было бы значение = 10.

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

теперь текст первоначального вопроса был:

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

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

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

к сожалению, этот вопрос все еще неясен или, возможно, имеет мало общего с атомными операциями. Во-первых, вы не можете "назначить [экземпляр класса] значение."Это просто не имеет смысла. Ля ссылка для экземпляра класса is значение, но нет никакого способа "назначить" что-либо самому экземпляру класса. Это большая разница по сравнению с типы значений, который can назначаются друг другу. Ты можешь!--22-->создать экземпляр с помощью 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
    // ???

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

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

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

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

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

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

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

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

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

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