Использование Interlocked.CompareExchange с классом - PullRequest
12 голосов
/ 14 июля 2011
Оператор

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

Например, 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 по-прежнему не выполняются.

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

Ответы [ 3 ]

20 голосов
/ 14 июля 2011

Нет.Это не может быть сделано.

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

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

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 101 * * .Он просто сравнивает ссылки, а затем меняет их.

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

3 голосов
/ 12 февраля 2013

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

2 голосов
/ 02 февраля 2017

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

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

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

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

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

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

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

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

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

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

Это означает, что фраза «на основе сравнения» является неполной относительно того, какими должны быть альтернативные действия.Глядя на предыдущую часть вопроса ОП, можно предположить, что вопрос состоит в том, чтобы условно манипулировать ссылками на экземпляры, а атомарность - это красная сельдь.Трудно понять, потому что, как отмечалось выше, 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; на самом деле все обстоит с точностью до наоборот: если вам действительно нужно выполнить элементарную операцию, которая выбирает одно из двух разных мест хранения или иным образом влияет на несколько ячеек памяти, просто использовать схему, в которой запутанные места обернуты в тривиальный класс , который затем дает вам единственную ссылку, которую можно атомарно заменить без блокировки. Кодирование без блокировок - простое занятие в .NET, так как оно менее хлопотно для объектов повторных попыток управления памятью в тех редких случаях, когда оптимистический путь терпит неудачу.

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

...