Вопрос по c#, multithreading, .net – Половина и полные заборы?

13

Я читал, чтоFull fences предотвращает любой вид переупорядочения команд или кэширования вокруг этого забора (через memoryBarrier)

Тогда я читаю оvolatile  который генерирует & # x201C; половину заборов & # x201D; :

The volatile keyword instructs the compiler to generate an acquire-fence on every read from that field, and a release-fence on every write to that field.

acquire-fence

An acquire-fence prevents other reads/writes from being moved before the fence;

release-fence

A release-fence prevents other reads/writes from being moved after the fence.

Может кто-нибудь объяснить мне эти 2 предложения на простом английском языке?

(where is the fence ?)

edit

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

http://i.stack.imgur.com/A5F7P.jpg enter image description here

Да, этот рисунок в значительной степени суммирует цель заборов выпуска-выпуска. Tudor
Если я не понимаю документацию, переменные переменные не требуют завершения всех записей. Вместо этого они требуют всех чтений для получения текущего значения, а не использования буферизованного значения, которое может быть не текущим, если другой поток изменил переменную. Trisped

Ваш Ответ

4   ответа
1

Что важно, когда вы читаете волатильное поле? Все предыдущие записи в это поле были зафиксированы.

Что важно, когда вы пишете в изменчивое поле? Что все предыдущие чтения уже получили свои значения.

Затем попытайтесь проверить, что в этих ситуациях имеет смысл приобретать ограждение и снимать ограждение.

1

энергозависимый (C # Reference):

The volatile keyword indicates that a field might be modified by multiple threads that are executing at the same time.

Чтобы программы работали быстрее, .NET иногда (обычно при оптимизации) делает умные вещи, например, не записывает переменную в память, если она будет изменена в следующей команде:

int i = 0;
//Do some stuff.
i++;
//Do some more stuff.
i--;
//Do other stuff.

Здесь компилятор будет хранить значение i в регистре доi--; завершено. Это экономит небольшое количество времени при получении значения из оперативной памяти.

При работе с потоками это не работает, если я разделен между потоками. Например, вы можете иметь:

//Thread 1:
i = 0;      //i is a volatile int shared between threads.
//Do some stuff.
//Wait for Thread 2 to read i.
i++;
//Do some more stuff.
//Wait for Thread 2 to set i = 12.
i--;
//Do other stuff.
//Use i for something like an index.

Если потоки 1 и 2 хранят i в регистрах, изменение i в потоке 1 не повлияет на i в потоке 2. Volatile сообщает компилятору, что эта переменная (i) может быть доступна из нескольких потоков. В результате он всегда должен получать текущее значение из памяти и записывать любые обновленные значения в память.

Другим примером является значение в таблице SQL, где любой может изменить значение в любое время. Нормальные переменные - это как запрос к таблице один раз, затем использование этого значения локально, пока вы не закончите с ним. Изменчивые переменные - это как запрос к таблице для получения / установки последнего значения каждый раз, когда вам нужно, чтобы у всех был доступ к текущему значению.

Посмотрите на пример вэнергозависимый (C # Reference) поскольку это хороший пример того, как использовать переменные.

Дайте нам знать, если вы хотите больше.

1

давайте предположим модель памяти, в которой возможно любое переупорядочение.

Давайте посмотрим на простой пример. Предположим, это изменчивое поле:

volatile int i = 0;

и эта последовательность чтения-записи:

1. int a = i;
2. i = 3;

Для инструкции 1, которая является чтениемi, забор приобретается. Это означает, что инструкция 2, которая является записью вi не может быть переупорядочен с помощью инструкции 1, поэтому нет возможностиa будет 3 в конце последовательности.

Теперь, конечно, вышесказанное не имеет особого смысла, если вы рассматриваете один поток, но если другой поток должен был работать с теми же значениями (предположим,a является глобальным):

thread 1               thread 2
a = i;                 b = a;
i = 3;

В этом случае вы бы подумали, что у потока 2 нет возможности получить значение 3 вb (так как он получит значениеa до или после назначенияa = i;). Однако, если читать и писатьi переупорядочить, возможно, чтоb получает значение 3. В этом случае созданиеi volatile необходим, если правильность вашей программы зависит отb не становится 3.

Disclaimer: Приведенный выше пример только для теоретических целей. Если компилятор не сошел с ума, он не стал бы делать перестановки, которые могли бы создать & quot; неправильно & quot; значение для переменной (т.е.a не может быть 3, даже еслиi не были летучими).

@ Ройи Намир: для более реалистичного примера того, почему заборы важны, посмотрите здесь:cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#whatismm, В моем примере я использовал переупорядочение записи с чтением одной и той же переменной, что на практике не может произойти, но может произойти переупорядочение чтения с чтением разных переменных.
Согласно моей ничьей (и описанию @Brian Gideon из спецификации) - при выполнении шага 1 создается забор ACQUIRE, а не освобождение ограды .... Royi Namir
13

на которую вы ссылаетесь, выглядит так, как я часто использую. Спецификация говорит это, хотя:

A read of a volatile field is called a volatile read. A volatile read has "acquire semantics"; that is, it is guaranteed to occur prior to any references to memory that occur after it in the instruction sequence. A write of a volatile field is called a volatile write. A volatile write has "release semantics"; that is, it is guaranteed to happen after any memory references prior to the write instruction in the instruction sequence.

Но я обычно использую формулировку, которую вы указали в своем вопросе, потому что я хочу сосредоточиться на том факте, что инструкцииcan be moved, Цитируемая вами формулировка и спецификация эквивалентны.

Я собираюсь представить несколько примеров. В этих примерах я собираюсь использовать специальную запись, которая использует & # x2191; стрелка для обозначения спускового ограждения и символа & # x2193; стрелка для обозначения ограждения. Никакая другая инструкция не может проходить мимо & # x2191; стрелка или вверх мимо & # x2193; стрела. Думайте о наконечнике стрелы как о отталкивающем от этого всем.

Рассмотрим следующий код.

static int x = 0;
static int y = 0;

static void Main()
{
  x++
  y++;
}

Переписать его, чтобы показать отдельные инструкции, будет выглядеть так.

static void Main()
{
  read x into register1
  increment register1
  write register1 into x
  read y into register1
  increment register1
  write register1 into y
}

Теперь, поскольку в этом примере нет барьеров памяти, компилятор C #, JIT или аппаратное обеспечение могут оптимизировать его различными способамиas long as the logical sequence as perceived by the executing thread is consistent with the physical sequence, Вот одна из таких оптимизаций. Обратите внимание, как читает и пишет в / изx а такжеy получил местами

static void Main()
{
  read y into register1
  read x into register2
  increment register1
  increment register2
  write register1 into y
  write register2 into x
}

Теперь это время изменит эти переменные наvolatile, Я буду использовать нашу стрелку для обозначения барьеров памяти. Обратите внимание, как порядок чтения и записи в / изx а такжеy сохранены Это связано с тем, что инструкции не могут преодолеть наши барьеры (обозначены как & # x2193; и & # x2191; головки стрелок). Теперь это важно. Обратите внимание, что приращение и записьx инструкции все еще было разрешено плавать вниз и чтениеy всплыл. Это все еще действует, потому что мы использовали половину заборов.

static volatile int x = 0;
static volatile int y = 0;

static void Main()
{
  read x into register1
  ↓    // volatile read
  read y into register2
  ↓    // volatile read
  increment register1
  increment register2
  ↑    // volatile write
  write register1 into x
  ↑    // volatile write
  write register2 into y
}

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

Теперь у нас также естьThread.MemoryBarrier метод для работы. Создает полный забор. Так что, если мы использовали нашу систему обозначений стрелок, мы можем визуализировать, как это работает.

Рассмотрим этот пример.

static int x = 0;
static int y = 0;

static void Main
{
  x++;
  Thread.MemoryBarrier();
  y++;
}

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

static void Main()
{
  read x into register1
  increment register1
  write register1 into x
  ↑    // Thread.MemoryBarrier
  ↓    // Thread.MemoryBarrier
  read y into register1
  increment register1
  write register1 into y
}

Хорошо, еще один пример. На этот раз давайте использовать VB.NET. VB.NET не имеетvolatile ключевое слово. Итак, как мы можем имитировать изменчивое чтение в VB.NET? Мы будем использоватьThread.MemoryBarrier.1

Public Function VolatileRead(ByRef address as Integer) as Integer
  Dim local = address
  Thread.MemoryBarrier()
  Return local
End Function

И вот как это выглядит с нашей стрелкой.

Public Function VolatileRead(ByRef address as Integer) as Integer
  read address into register1
  ↑    // Thread.MemoryBarrier
  ↓    // Thread.MemoryBarrier
  return register1
End Function

Важно отметить, что, поскольку мы хотим подражать волатильным, прочитайте призыв кThread.MemoryBarrier должны быть размещеныafter фактическое чтение. Не попадайтесь в ловушку, думая, что изменчивое чтение означает «свежее чтение» и энергозависимая запись означает «совершенную запись». Это не то, как это работает, и это, конечно, не то, что описывает спецификация.

Update:

По ссылке на изображение.

wait! I am verifing that all the Writes are finished!

а также

wait! I am verifying that all the consumers have got the current value!

Это та ловушка, о которой я говорил. Заявления не совсем точны. Да, барьер памяти, реализованный на аппаратном уровне, может синхронизировать строки когерентности кэша, и в результате вышеприведенные утверждения могут быть в некоторой степени точным объяснением того, что происходит. Но,volatile не делает ничего, кроме как ограничить движение инструкций. В спецификации сказаноnothing о загрузке значения из памяти или сохранении его в памяти в том месте, где находится барьер памяти.

1There is, of course, the Thread.VolatileRead builtin already. And you will notice that it is implemented exactly as I have done here.

@RoyiNamir: представь, что будет без звонкаThread.MemoryBarrier, Затем компилятор может упростить код до одной строкиReturn address, А затем, продолжая, и поскольку функция проста, она, вероятно, будет встроена, по существу, превращая весь вызов функции в обычное чтение из памяти в соответствии со всеми распространенными стратегиями переупорядочения.
@KaseySpeakman: Спасибо. Обязательно прочитайте мой ответhere для аналогичного объяснения, за исключением того случая, когда речь идет о двойной проверке схемы блокировки. Я должен быть честным. Это заняло у меняreally задолго до того, как все эти барьеры памяти наконец-то "щелкнули" со мной.
Брайан ты можешьplease взглянуть на мой вопрос? (и, конечно, опубликовать ответ, я знаю, что вы мастер потоков).stackoverflow.com/questions/15032297/deadlock-clarification Royi Namir
@HamletHakobyan: Хорошо, у меня была возможность поближе взглянуть на это так, как я это делал изначально. Как ни странно, VB.NET позволяет ставить операторы послеReturn и он будет компилироваться без ошибок или предупреждений. Но он не выполнит любое утверждение, появившееся послеReturn потому что он вводитbr.s Ил инструкции перепрыгнуть через них. Странный. Вы бы подумали, что будет хотя бы предупреждение.
@RoyiNamir: если вы хотите & quot; свежее чтение & quot; тогда вам придется позвонитьThread.MemoryBarrier перед прочтением. Но это не то же самое, что изменчивое чтение. Что изменчивое чтение гарантирует, что следующее чтение будет более новым, чем предыдущее. Это не гарантирует, что текущее чтение вернет последнее значение.

Похожие вопросы