Попытка понять связь между Thread.MemoryBarrier() и переключением контекста

Поскольку кажется, что переключение контекста может произойти в любой момент выполнения инструкций, мне теперь интересно, почему код «в рассматриваемой части» (эти две инструкции) имеет смысл, если переключение контекста может произойти между любыми инструкциями, и мы можем быть на разных ЦП core во второй инструкции.

void B()
  {
    Thread.MemoryBarrier();    // Barrier 3
    if (_complete)
    {
      //PART IN QUESTION
      Thread.MemoryBarrier();       // Barrier 4
      Console.WriteLine (_answer);
      //END PART IN QUESTION
    }
  }

Описание MemoryBarrier здесь не дает гарантий MemoryBarrier что процессоры не будут переключаться после его вызова.

(это связано с этим вопросом)


person Valentin Kuzub    schedule 31.08.2011    source источник


Ответы (2)


Что гарантирует, что переключение контекста не произойдет после вызова Thread.MemorryBarrier()?

Ничего такого. MemoryBarriers не препятствует переключению контекста (или атомарному выполнению вашего кода).

Что касается вашего другого вопроса, зачем нужен Барьер 4:

В примере кода из предыдущего вопроса компилятор C#, среда CLR или ЦП могут переупорядочить чтение переменной ответа перед завершенной переменной, если барьера 4 не было. то есть код, который фактически работает, может быть похож на:

Thread.MemoryBarrier();    // Barrier 3
int tmpanswer = _answer;
if (_complete)
{

  Console.WriteLine (tmpanswer);
}

Барьер перед Console.WriteLine() предотвратит чтение _answer перед чтением _completed

Но имейте в виду, что пример кода предоставляет только эту единственную гарантию относительно кода в void B() (при условии, что A() запускается только один раз )

  • Если переменная _complete имеет значение true, то Console.WriteLine выведет 123, а не 0.

Таким образом, если A и B не запускаются последовательно, код не обеспечивает никакой блокировки/уведомления, так что B всегда будет печатать 123. A() и B() могут быть чередованы/прерваны в любое время при его выполнении - вы не можете контролировать кто когда бежит.

Нет никакой гарантии, что B() запустится после A(), независимо от того, в каком порядке вы запустили 2 потока. (Хотя где-то еще в коде вы можете сначала запустить A() и явно дождаться его завершения, прежде чем запускать B () конечно)

person Lyke    schedule 31.08.2011
comment
Я думал, что переупорядочение может произойти, если инструкции находятся на том же логическом уровне, что и {a=b; с=г; d=e;} не в IF, как здесь, где кажется логичным всегда сначала читать полностью, потому что во многих случаях ответ не потребуется. Вы уверены, что такое переоформление возможно? - person Valentin Kuzub; 31.08.2011
comment
@Valentin Kuzub Переупорядочение доступа к памяти не имеет ничего общего с логическими/блочными уровнями вашего кода C#. - person Lyke; 31.08.2011

Нет никаких гарантий, что переключение контекста произойдет или не произойдет вокруг MemoryBarrier. Это ортогональные понятия.

person JaredPar    schedule 31.08.2011
comment
ну не могли бы вы немного уточнить? если вы посмотрите на связанный вопрос, в методе B есть 2 вызова memorybarrier, и в нем сказано, что второй необходим, чтобы гарантировать, что мы отключим кеширование перед чтением переменных ответа в случае переключения контекста, но если они ортогональны, я думаю, что барьер Тогда 3 должно хватить или?... - person Valentin Kuzub; 31.08.2011
comment
Это просто разные понятия. MemoryBarrier - это заборы, которые просто ограничивают возможность переупорядочивания инструкций чтения и записи. Они просто не могут перепрыгнуть через забор. Переключение контекста просто меняет текущий поток. Это может произойти непосредственно перед барьером или после него (или действительно в любое время, когда ему заблагорассудится). - person JaredPar; 31.08.2011
comment
Если вам нужно защитить переменную от нескольких операций чтения/записи, я предлагаю использовать файл lock. - person user7116; 31.08.2011
comment
это имеет смысл для меня, но когда вы смотрите на связанный вопрос, есть вызов барьера памяти 3, который уже отключает переупорядочение инструкций, и этот метод только для чтения, а не для записи, и есть, если почему этого недостаточно, и мб4 звонок нужен? Или возможно чтение ответа происходит до завершения чтения, если MB4 нет?? это было бы новостью для меня. - person Valentin Kuzub; 31.08.2011
comment
@ sixlettervariables я пытаюсь понять барьер памяти, блокировка довольно проста, в отличие от барьера, для меня - person Valentin Kuzub; 31.08.2011
comment
Барьеры памяти @Valentin трудно суммировать полностью в сообщении SO. Короче говоря, с вашей точки зрения, компилятор/ЦП может переупорядочивать инструкции чтения и записи в рамках определенного набора правил. т. е. если чтение A и запись B не связаны, они могут выполняться в любом порядке (чтение/запись или запись/чтение). Барьер памяти предотвращает переупорядочивание, чтобы пересечь его. Таким образом, чтение A, барьер памяти, запись B заставят его выполняться при чтении A, а затем при записи B. Это может быть важно для определенных типов алгоритмов (особенно для алгоритмов без блокировки) - person JaredPar; 31.08.2011
comment
Спасибо, это имеет смысл для меня, Джаред. Но теперь, когда я отредактировал вопрос, можете ли вы сказать, как MB4 помогает коду так, как MB3 не помогает? Я думаю, что на это можно ответить в сообщении SO. В моем первоначальном вопросе я сказал это, потому что нам нужно очистить кеш перед чтением ответа, на случай, если переключение контекста произошло между вызовами IF и CONSOLE, но если концепции являются ортогональными вызовами MB3, должно быть достаточно уже - person Valentin Kuzub; 31.08.2011
comment
@Valentin Валентин, я прочитал и ваш вопрос, и указанный пост в блоге. На самом деле неясно, чего нужно достичь в сообщении в блоге, и, следовательно, трудно понять, зачем им нужны барьеры памяти. - person JaredPar; 31.08.2011
comment
Я думаю, что цель состояла в том, чтобы обеспечить атомарность методов A и B, мы либо 1) читаем complete==true И имеем правильный ответ, либо 2) читаем не завершенный и не проверяем ответ, в зависимости от того, какой поток A или B выполняется во-первых, если это было неопределенно (например, используя Parallel.Invoke(A,B)) - person Valentin Kuzub; 31.08.2011
comment
@ Валентин, я не уверен, согласен ли я с образцом. Я бы поверил, что нужен только один из барьеров (после записи в _answer). Остальные лишние. Аргумент о свежести бесполезен, IMOH. Я должен был бы прочитать больше об этом. - person JaredPar; 31.08.2011
comment
Барьер 1 необходим для предотвращения записи _completed перед _answer. Барьер 4 необходим для чтения _completed перед _answer. Барьер 3 имеет очень сомнительную ценность. Я был бы немного удивлен, если бы завершение потока не вызвало барьер памяти в C#. Если это не так, причина для Барьера 2 (который будет синхронизировать кеши, не нужные для x86, но необходимые для Itanium) будет заключаться в том, что если вы где-то явно запустите A сначала в одном потоке, дождитесь его завершения, затем запустите B, тогда B увидит обновленные значения. - person Lyke; 31.08.2011
comment
@ Лайк, а, да, на Барьере № 4. Я постоянно забываю о проблеме чтения-переупорядочивания. - person JaredPar; 31.08.2011