9

Чтобы реализовать блокировки свободного кода для многопоточного приложения я использовал volatile переменные, Теоретически: volatile ключевое слово просто используется, чтобы убедиться, что все потоки увидеть самую последнюю величину а изменчивая переменная; поэтому, если поток A обновляет значение переменной и поток B читает эту переменную сразу после этого обновления, он увидит самое обновленное значение, которое недавно было написано из потока A. Как я читал в C# 4.0 в двух словах книга, которая это неправильно потомуЛетучие и Thread.MemoryBarrier в C#

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

Может эта проблема решается путем ввода Thread.MemoryBarrier() перед каждым ГЭТ в volatile переменной как:

private volatile bool _foo = false; 

private void A() 
{ 
    //… 
    Thread.MemoryBarrier(); 
    if (_foo) 
    { 
     //do somthing 
    } 
} 

private void B() 
{ 
    //… 
    _foo = true; 
    //… 
} 

И если это решает проблему; рассмотрим, что у нас есть цикл while, который зависит от этого значения в одном из его условий; ставит Thread.MemoryBarrier() до того, как цикл while является правильным способом исправить проблему? Пример:

private void A() 
{ 
    Thread.MemoryBarrier(); 
    while (_someOtherConditions && _foo) 
    { 
     // do somthing. 
    } 
} 

Чтобы быть более точным, я хочу, чтобы переменная _foo дать свою самую свежую ценность, когда любой поток с просьбой к нему в любое время; так что если вставить Thread.MemoryBarrier() перед вызовом переменной исправляет проблему, то я мог бы использовать Foo собственность вместо _foo и сделать Thread.MemoryBarrier() в ГЭТ этой собственности, как:

Foo 
{ 
    get 
    { 
     Thread.MemoryBarrier(); 
     return _foo; 
    } 
    set 
    { 
     _foo = value; 
    } 
} 
+0

Возможный дубликат ... http: //stackoverflow.com/questions/1330590/when-to-use-volatile-or-thread-memorybarrier-in-threadsafe-locking-code-c –

+0

@Aron; нет, он не дублируется; это еще один вопрос. –

+0

@Jalal Вы пытаетесь решить проблему, используя этот подход volatile/memorybarrier, или вы в порядке с любым подходом, который решает вашу проблему? –

ответ

8

«C# В двух словах» правильный, но его утверждение является спорным. Зачем?

  • A «запись» сопровождаемая «читать», withot «летучие», гарантированно происходит в программе порядка в любом случае , если это влияет логик в одном потоке
  • «Запись» перед «читать» в многопоточной программе совершенно бессмысленно беспокоиться в вашем примере.

Давайте уточним. Возьмите исходный код:

private void A() 
{ 
    //… 
    if (_foo) 
    { 
     //do something 
    } 
} 

Что произойдет, если планировщик потоков уже проверил переменную _foo, но она будет приостановлена ​​как раз перед //do something комментарием? Ну, в этот момент ваш другой поток может изменить значение _foo, а это значит, что все ваши летучие и Thread.MemoryBarriers ничего не посчитали !!! Если абсолютно необходимо избегать do_something, если значение _foo неверно, тогда у вас нет выбора, кроме как использовать блокировку.

Однако, если это нормально для do something для выполнения, когда вдруг _foo становится фальшивым, значит, ключевое слово volatile было более чем достаточно для ваших нужд.

Чтобы быть ясным: все респонденты, которые говорят вам использовать барьер памяти, являются неправильными или обеспечивают излишнюю реакцию.

+0

спасибо за ваш ответ; В моем случае я хочу избежать do_something, который будет выполнен, если _foo был как можно больше, но это не смертельно критично **; но если бы я мог сделать его более точным с MemoryBarrier, то ** почему бы не использовать его с volatile. Если расписание потоков приостанавливает поток после проверки _foo «ведьма возможна», то do_somthing будет выполняться, но не менее мы ** уменьшаем шансы **, что _foo будет ложным в то время? –

+2

@Jalal: декларация 'volatile' уже gaurantees, сама по себе, что переменная не будет храниться в регистре CPU. Он обеспечивает прямую запись и запись в память (что делает недействительным кеш других процессоров на многопроцессорной машине). Поэтому явный барьер памяти не нужен; «volatile» уже выполняет то, что вам нужно. –

+0

@BrentArias MemoryBarrier * необходимо *, потому что процессор может обновлять значение на одном процессоре, а не на другом, поэтому один поток считывает обновленное значение, а другой поток считывает устаревшее значение. – Anthony

0

В вашем втором примере, вам нужно будет также положить a Thread.MemoryBarrier(); внутри цикла, чтобы убедиться, что вы получаете последнее значение каждый раз, когда вы проверяете условие цикла.

+0

, если это устранит проблему, тогда установка Thread.MemoryBarrier() должна быть на последней строке перед закрывающей скобкой цикла while и перед вызовом цикла while. –

+0

@Jalal: Я вполне уверен, что явный 'MemoryBarrier 'вызовы не требуются в этих примерах, если' _foo' отмечен как 'volatile'. (Я не эксперт, хотя я бы, вероятно, просто использовал «lock».) – LukeH

+2

В этом случае использование Thread.MemoryBarrier, будь то до, после или и того и другого, не достигает ничего, что волатильно не было достигнуто , –

0

Вытащил из here ...

class Foo 
{ 
    int _answer; 
    bool _complete; 

    void A() 
    { 
    _answer = 123; 
    Thread.MemoryBarrier(); // Barrier 1 
    _complete = true; 
    Thread.MemoryBarrier(); // Barrier 2 
    } 

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

Барьеры 1 и 4 предотвратить этот пример от написания «0». Барьеры 2 и 3 обеспечивают гарантию свежести: они гарантируют, что если B пробег после A, то значение _complete будет равно true.

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

собственных слова
private void A() 
{ 
    Thread.MemoryBarrier(); 
    while (_someOtherConditions && _foo) 
    { 
     //do somthing 
     Thread.MemoryBarrier(); 
    } 
} 
+1

@ Aaron Спасибо. Я проверяю эту ссылку до того, как она находится в той же книге «C# 4.0 в двух словах». Однако если это устранит проблему, тогда установка Thread.MemoryBarrier() должна быть на последней строке перед закрывающей скобкой цикла while , –

+0

@Jalal Это зависит от того, что делает ваше «делать что-то», где теперь находится ThreadBarrier, влияет на ваши _someOtherconditions/_foo vars –

+1

@Aaron: Если '_foo' помечен как' volatile', то я уверен, что явный 'MemoryBarrier' звонки в этих конкретных примерах не нужны. (Довольно уверен, но не на 100% уверен, и если сомневаюсь, я бы, вероятно, просто использовал «lock».) – LukeH

0

от Microsoft на барьерах памяти: требуется

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

Для большинства целей оператор C# блокировки, оператор Visual Basic SyncLock или класс Monitor предоставляют более простые способы синхронизации данных.

+0

Обратитесь к http://www.albahari.com/threading/part4.aspx, чтобы убедиться, что собственные слова Microsoft не верны. –

+0

Плакат специально запрашивает реализацию блокировки. –

5

Книга .
Модель памяти CLR указывает, что операции загрузки и хранения могут быть переупорядочены. Это относится к нестабильным переменным и.

Объявление переменной как volatile означает только то, что операции загрузки будут иметь приобретают семантики, и хранить операции будут иметь релиз семантику. Кроме того, компилятор избегает выполнения определенных оптимизаций, которые ретранслируют по тому факту, что к переменной обращаются в сериализованном однопоточном режиме (например, подъемная нагрузка/сохранение из циклов).

Использование ключевого слова volatile не создает критические разделы, и это не приводит к тому, что потоки магически синхронизируются друг с другом.

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

+0

@Liran: Я знал, что использование волатильности в одиночку не создает критических разделов и не вызывает синхронизацию потоков друг с другом. Я просто прошу переменную _foo быть обновленной, когда любой поток запрашивает ее в любое время в пределах блокировка бесплатный код. –

+0

@Jalal, объявив переменную как изменчивую, гарантирует, что каждая операция загрузки будет считывать самое обновленное значение переменной (например, из основной памяти вместо регистра). Вы не должны распространять барьеры памяти по всему вашему коду ... это может иметь серьезное негативное влияние на производительность. Опять же, если вы не просто пытаетесь поиграть и поэкспериментировать с кодом, свободным от блокировки, и на картинке есть реальный производственный код ... Я рекомендую вам найти другое решение. – Liran

+0

@Liran; «объявление переменной как изменчивой будет гарантировать, что каждая операция загрузки будет читать самое обновленное значение переменной»; как я понял из книги http://www.albahari.com/threading/part4.aspx, что wirte, прочитанное, возможно, не получит самое последнее значение, и да, я пытаюсь поэкспериментировать с кодом, свободным от блокировки; возникает вопрос: является ли это большим отрицательным эффектом на производительность приложения, когда выполняет «Thread.MemoryBarrier» до того, как какая-либо операция получения изменчивой переменной будет получена, если бы я не смог поместить ее в свойство до получения любой переменной.пожалуйста, ознакомьтесь с обновлением вопроса –