присвоение ссылки является атомарным, так почему Interlocked.Exchange (ref Object, Object) необходим - PullRequest
96 голосов
/ 03 февраля 2010

В моем многопоточном веб-сервисе asmx у меня было поле класса _allData моего собственного типа SystemData, которое состоит из нескольких List<T> и Dictionary<T>, помеченных как volatile. Системные данные (_allData) обновляются время от времени, и я делаю это, создавая еще один объект с именем newData и заполняя его структуры данных новыми данными. Когда это сделано, я просто назначаю

private static volatile SystemData _allData

public static bool LoadAllSystemData()
{
    SystemData newData = new SystemData();
    /* fill newData with up-to-date data*/
     ...
    _allData = newData.
} 

Это должно работать, поскольку назначение является атомарным, и потоки, имеющие ссылку на старые данные, продолжают его использовать, а остальные получают новые системные данные сразу после назначения. Однако мой коллега сказал, что вместо использования ключевого слова volatile и простого назначения я должен использовать InterLocked.Exchange, потому что он сказал, что на некоторых платформах не гарантируется, что присвоение ссылок является атомарным. Более того: когда я объявляю the _allData поле как volatile

Interlocked.Exchange<SystemData>(ref _allData, newData); 

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

Ответы [ 4 ]

164 голосов
/ 03 февраля 2010

Здесь много вопросов. Рассматривая их по одному:

Ссылочное присвоение является атомарным, так зачем Interlocked.Exchange (ref Object, Object)?

Ссылочное присвоение является атомарным. Interlocked.Exchange не делает только ссылки. Он читает текущее значение переменной, скрывает старое значение и назначает новое значение переменной, все как атомарная операция.

мой коллега сказал, что на некоторых платформах не гарантируется, что задание ссылки является атомарным. Был ли мой коллега прав?

Нет. Назначение ссылок гарантированно будет атомарным на всех платформах .NET.

Мой коллега рассуждает из ложных посылок. Значит ли это, что их выводы неверны?

Не обязательно. Ваш коллега может дать вам хороший совет по плохим причинам. Возможно, есть какая-то другая причина, почему вы должны использовать Interlocked.Exchange. Программирование без блокировок безумно сложно, и в тот момент, когда вы отказываетесь от устоявшихся практик, поддерживаемых экспертами в этой области, вы теряете травку и рискуете наихудшими условиями гонки. Я не являюсь ни экспертом в этой области, ни экспертом в вашем коде, поэтому я не могу судить так или иначе.

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

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

Причина, по которой компилятор выдает это предупреждение, заключается в том, что пометка поля как энергозависимого означает, что «это поле будет обновляться в нескольких потоках - не генерируйте код, который кэширует значения этого поля, и убедитесь, что любое чтение или записи этого поля не "перемещаются вперед и назад во времени" из-за несогласованности кеша процессора ".

(Я предполагаю, что вы уже все это понимаете. Если у вас нет детального понимания значения volatile и его влияния на семантику кэша процессора, то вы не понимаете, как оно работает, и не должны использовать volatile. Lock - бесплатные программы очень сложно понять правильно, убедитесь, что ваша программа правильная, потому что вы понимаете, как она работает, а не случайно.)

Теперь предположим, что вы создаете переменную, которая является псевдонимом энергозависимого поля, передавая ссылку в это поле. Внутри вызываемого метода у компилятора нет никаких оснований знать, что ссылка должна иметь изменчивую семантику! Компилятор с радостью сгенерирует код для метода, который не может реализовать правила для изменчивых полей, но переменная является изменяемым полем. Это может полностью разрушить вашу логику без блокировки; всегда предполагается, что к летучему полю всегда обращаются с изменчивой семантикой. Нет смысла рассматривать это как изменчивое время от времени, а не другие времена; вы должны всегда быть последовательными, иначе вы не сможете гарантировать согласованность при других доступах.

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

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

9 голосов
/ 03 февраля 2010

Либо ваш коллега ошибается, либо он знает что-то, чего нет в спецификации языка C #.

5.5 Атомность ссылок на переменные :

"Читает и пишет следующее типы данных являются атомарными: bool, char, байт, sbyte, короткий, ushort, uint, int, float и ссылочные типы. "

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

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

6 голосов
/ 03 февраля 2010

Блокировано. Обмен

Устанавливает переменную указанного типа T в указанное значение и возвращает исходное значение в качестве атомарной операции.

Оно меняет и возвращает исходное значение, оно бесполезно, потому что вы хотите изменить его, и, как сказал Гуффа, оно уже атомарное.

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

2 голосов
/ 29 декабря 2016

Iterlocked.Exchange() не просто атомарный, он также заботится о видимости памяти:

Следующие функции синхронизации используют соответствующие барьеры для обеспечения упорядочения памяти:

Функции, которые входят или выходят из критических секций

Функции, которые сигнализируют об объектах синхронизации

Функции ожидания

Блокированные функции

Проблемы с синхронизацией и многопроцессорностью

Это означает, что в дополнение к атомарности оно обеспечивает:

  • Для потока, вызывающего его:
    • Переупорядочивание инструкций не выполняется (компилятором, средой выполнения или оборудованием).
  • Для всех тем:
    • Нет чтения в память, которое происходит до того, как эта инструкция увидит изменения, внесенные этой инструкцией.
    • Все чтения после этой инструкции увидят изменения, внесенные этой инструкцией.
    • Все записи в память после того, как эта инструкция произойдет после того, как это изменение инструкции достигнет основной памяти (путем сброса этого изменения инструкции в основную память, когда оно выполнено, и не позволяя аппаратным средствам сбрасывать свои собственные значения по времени).
...