Разъяснение Interlocked.Exchange

У меня есть несколько простых (надеюсь) вопросов, на которые я не смог найти ответы:

Скажем, у меня есть объекты a, b, которые доступны для нескольких потоков.

Interlocked.Exchange(ref a, b)

Если «b» не является изменчивым, будет ли эта операция рассматривать его как таковое? то есть возьмет ли он последнее значение этой переменной из памяти? Если да, то чтение является «атомарным» с записью? Я понимаю, что основная цель Interlocked.Exchange заключается в том, что вы получите предыдущее значение «a» как атомарную операцию с новой записью. Но моя главная путаница связана с тем, какое значение «b» на самом деле записывается в «a».

Мой второй вопрос связан с цитатой из этой статьи:

http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/

«Одним интересным моментом является то, что все записи в C# являются энергозависимыми в соответствии с моделью памяти, описанной здесь и здесь, и также предположительно реализованы как таковые. Спецификация ECMA языка C# на самом деле определяет более слабую модель, в которой записи не являются энергозависимыми по умолчанию. ."

Это правда? Если да, то есть ли цель Interlocked.Exchange, если предыдущее значение «а» не волнует? (относительно моего первого примера). Я не вижу никаких других статей или комментариев на StackOverflow о том, что каждая запись нестабильна. Я понимаю, однако, что записи являются атомарными.

Изменить: если ответ на мой первый вопрос заключается в том, что «b» не рассматривается как изменчивый, а ответ на мой второй вопрос заключается в том, что записи действительно являются изменчивыми, то последующее действие: когда блокируется. обмен полезен, если мы не не заботится о предыдущем значении «а»?


person user981225    schedule 22.10.2012    source источник
comment
«Волатильность» b не должна иметь значения.   -  person Henk Holterman    schedule 22.10.2012


Ответы (3)


переменная, переданная в Exchange (или любая изменчивая переменная, переданная в любой метод), не сохраняет «изменчивость» при передаче... На самом деле нет необходимости в том, чтобы она была volatile (на время вызова метода), потому что единственное, что volatile делает, чтобы убедиться, что компилятор (ы) не оптимизирует использование переменной (что обычно означает оптимизацию записи в регистры, чтобы значение могло быть «увидено» только одним процессором). На процессорах, отличных от x86/x64, это иногда означает инструкции, которые гарантируют получение или освобождение семантики. .NET не использует регистры для передачи аргументов, поэтому volatile не может повлиять на «изменчивость» передаваемых аргументов. Он должен всегда получать самое последнее значение из памяти из-за гарантий видимости модели памяти.

RE вопрос 2: цитата «вроде» верна, в зависимости от объявления поля существуют гарантии видимости w.r.t. поля; но без «изменчивого» доступа к полю можно оптимизировать доступ к регистру на определенных этапах использования, потенциально скрывая определенные записи от других процессоров.

Interlocked обмены заставляют операции, которые не были атомарными, казаться атомарными. Обмен по своей природе подобен:

var x = someVariable;
someVariable = y;

Это не может быть атомарным независимо от типа someVariable. Exchange делает эту операцию атомарной. Это также атомарно с неатомарными типами, такими как double, long (в 32-разрядных версиях) и т. д.

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

Зачем вам использовать Exchange, если вас не волнует предыдущее значение «а»? Если вас не интересует реальный «обмен», то VolatileWrite кажется более подходящим.

В качестве альтернативы, если «обмен» не нужен, вы можете написать потокобезопасный код для модели «A = B» следующим образом:

Thread.MemoryBarrier();
A=B;

FWIW, Interlocked частично смоделирован на основе инструкций сравнения и замены (CAS) в некоторых процессорах. Эти инструкции позволяют вам выполнять эти две операции в одной инструкции (что делает ее атомарной). Без таких вещей, как Interlocked, компилятору было бы трудно сделать вывод, что следует использовать одну из этих инструкций CAS. Кроме того, Interlocked обеспечивает атомарное использование на процессорах, которые не поддерживают эти инструкции CAS (и другие потенциально неатомарные инструкции, такие как inc и dec, которые могут быть доступны не на всех процессорах).

person Peter Ritchie    schedule 22.10.2012
comment
Спасибо за ваш ответ. относительно вашего вопроса о том, почему вы хотите использовать обмен, если вас не волнует предыдущее значение a - Что, если B не является изменчивым, но мы хотим убедиться, что мы не устанавливаем A в кэшированное значение B? И если вопрос 2 только «отчасти» верен, не будет ли установка A таким образом потенциально отличаться от простой установки A = B? - person user981225; 22.10.2012
comment
Если вас не волнует предыдущее значение, Volatile.Write может быть более подходящим, хотя я не думаю, что оно поддерживает все те же типы, что и Exchange. Всегда безопаснее использовать VolatileWrite или Interlocked.Exchange (или lock или volatile) для записи значения в поле, которое может быть видно другим потокам. - person Peter Ritchie; 22.10.2012

Если «b» не является изменчивым, будет ли эта операция рассматривать его как таковое?

Да, потому что, согласно этому источнику, все методы класса Interlocked генерируют неявные ограничения памяти.

person Tudor    schedule 22.10.2012
comment
Спасибо. Означает ли это также, что чтение значения B является атомарным с записью этого значения в A? Или можно ли технически изменить значение B до того, как оно будет присвоено A? - person user981225; 22.10.2012
comment
@ user981225: Ну, b передается методу по значению, поэтому любое изменение после вызова метода не повлияет на значение, полученное методом. - person Tudor; 22.10.2012

Если «b» не является изменчивым, будет ли эта операция рассматривать его как таковое?

Я не думаю, что вам следует использовать это, когда b является общей переменной. Так что это снимает всю проблему. Но Exchange всегда будет использовать Memorybarrier, поэтому ответ, вероятно, будет утвердительным.

когда interlocked.exhange полезен, если нам не важно предыдущее значение 'a'?

Перегрузка для double очень полезна, потому что запись в double в противном случае не является атомарной. То же самое для Int64 в 32-битных системах.

Но для перегрузок Exchange() для атомарных типов вариант использования менее ясен. Я думаю, что большинство алгоритмов предпочтут CompareExcange() .

Так что считайте это атомарной Write().

person Henk Holterman    schedule 22.10.2012
comment
Спасибо. Не могли бы вы объяснить, почему вы не думаете, что это следует использовать, когда b является общей переменной? - person user981225; 22.10.2012
comment
Переверните: когда/почему вы записываете общий B в общий A? - person Henk Holterman; 22.10.2012
comment
Логично, когда вы так говорите :) - person user981225; 22.10.2012