помогает ли мьютекс избавиться от изменчивого ключевого слова? - PullRequest
9 голосов
/ 24 октября 2009

У меня есть класс блокировки нескольких R / W, который хранит счетчики чтения, записи и ожидания чтения, ожидания записи. Мьютекс защищает их от нескольких потоков.

У меня вопрос: нужно ли нам объявить счетчики как энергозависимые, чтобы компилятор не испортил их во время оптимизации.

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

Я понимаю, что мьютекс - это механизм времени выполнения для синхронизации, а ключевое слово "volatile" - это указатель времени компиляции для правильной работы во время оптимизации.

С уважением, -Jay.

Ответы [ 5 ]

15 голосов
/ 05 ноября 2009

Здесь есть 2 в основном не связанных между собой предмета, которые всегда запутываются.

  • летучий
  • темы, блокировки, барьеры памяти и т. Д.

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

Барьеры памяти (предоставляемые мьютексами, блокировками и т. Д.), Как цитировал Херб Саттер в другом ответе, предназначены для предотвращения CPU от переупорядочения запросов памяти на чтение / запись независимо от того, как компилятор сказал сделай это. т.е. не оптимизируйте, не используйте короткие пути - на уровне процессора.

Подобные, но на самом деле очень разные вещи.

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

Обычные вызовы функций, влияющие на оптимизацию:

external void library_func(); // from some external library

global int x;

int f()
{
   x = 2;
   library_func();
   return x; // x is reloaded because it may have changed
}

, если компилятор не может проверить library_func () и определить, что он не касается x, он будет перечитывать x при возврате. Это даже БЕЗ летучих.

Threading:

int f(SomeObject & obj)
{
   int temp1;
   int temp2;
   int temp3;

   int temp1 = obj.x;

   lock(obj.mutex); // really should use RAII
      temp2 = obj.x;
      temp3 = obj.x;
   unlock(obj.mutex);

   return temp;
}

После прочтения obj.x для temp1, компилятор собирается перечитать obj.x для temp2 - НЕ из-за магии блокировок, а потому, что неясно, изменил ли lock () объект obj. Возможно, вы могли бы установить флаги компилятора для агрессивной оптимизации (без псевдонимов и т. Д.) И, таким образом, не перечитывать x, но тогда часть вашего кода, вероятно, начнет давать сбой.

Для temp3 компилятор (надеюсь) не будет перечитывать obj.x. Если по какой-либо причине obj.x может измениться между temp2 и temp3, тогда вы будете использовать volatile (и ваша блокировка будет нарушена / бесполезна).

Наконец, если ваши функции lock () / unlock () были как-то встроены, возможно, компилятор мог бы оценить код и увидеть, что obj.x не изменился. Но я гарантирую одно из двух: - встроенный код в конечном итоге вызывает некоторую функцию блокировки на уровне ОС (что препятствует оценке) или - вы вызываете некоторые инструкции по защите памяти asm (то есть, которые заключены во встроенные функции, такие как __InterlockedCompareExchange), которые ваш компилятор распознает и, таким образом, избегает переупорядочения.

РЕДАКТИРОВАТЬ: P.S. Я забыл упомянуть - для вещей pthreads некоторые компиляторы помечены как «POSIX-совместимые», что означает, среди прочего, что они будут распознавать функции pthread_ и не будут делать плохих оптимизаций вокруг них. т. е. хотя в стандарте C ++ пока не упоминаются потоки, эти компиляторы (хотя бы минимально).

Итак, короткий ответ

вам не нужно летучих.

13 голосов
/ 24 октября 2009

Из статьи Херба Саттера «Используйте критические участки (предпочтительно замки) для устранения рас» (http://www.ddj.com/cpp/201804238):

Таким образом, чтобы преобразование переупорядочения было действительным, оно должно учитывать критические разделы программы, соблюдая одно ключевое правило критических разделов: код не может выйти из критического раздела. (Код всегда подходит для перемещения.) Мы применяем это золотое правило, требуя симметричной семантики одностороннего забора для начала и конца любого критического раздела, показанного стрелками на рисунке 1:

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

Таким образом, для компилятора для создания правильного кода для целевой платформы, когда критическая секция вводится и выходит (и термин критическая секция используется в его общем смысле, не обязательно в смысле Win32 чего-то защищенного * 1014) * структура - критическая секция может быть защищена другими объектами синхронизации) должна соблюдаться правильная семантика получения и выпуска. Поэтому вам не нужно помечать общие переменные как изменчивые, если они доступны только в защищенных критических секциях.

5 голосов
/ 24 октября 2009

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

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

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

Держите их нестабильными.

4 голосов
/ 24 октября 2009

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

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

РЕДАКТИРОВАТЬ: Настоящим я подтверждаю Тони ответ , как лучше, чем мой.

3 голосов
/ 24 октября 2009

Вам все еще нужно ключевое слово "volatile".

Мьютексы предотвращают одновременный доступ к счетчикам.

"volatile" указывает компилятору фактически использовать счетчик вместо того, чтобы кэшировать его в регистр процессора (который не обновляться параллельной веткой).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...