помогает ли наличие мьютекса избавиться от изменчивого ключевого слова?

StackOverflow https://stackoverflow.com/questions/1616093

Вопрос

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

Мой вопрос в том, нужно ли нам по-прежнему объявлять счетчики как изменяемые, чтобы компилятор не испортил их при выполнении оптимизации.

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

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

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

Это было полезно?

Решение

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

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

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

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

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

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

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

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 при возврате.Это даже БЕЗ УЧЕТА volatile.

Нарезание резьбы:

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.Вероятно, вы могли бы установить флаги компилятора для агрессивной оптимизации (no-alias и т.д.) И, таким образом, не перечитывать x, но тогда часть вашего кода, вероятно, начнет давать сбой.

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

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

Редактировать:P.S.Я забыл упомянуть - для материалов pthreads некоторые компиляторы помечены как "совместимые с POSIX", что означает, среди прочего, что они будут распознавать функции pthread_ и не выполнять плохую оптимизацию вокруг них.т.е. даже несмотря на то, что стандарт C ++ еще не упоминает потоки, эти компиляторы это делают (по крайней мере, минимально).

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

вам не нужна изменчивость.

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top