C ++ volatile ключевое слово с глобальной переменной общего доступа, доступной по функции - PullRequest
5 голосов
/ 06 сентября 2010

У меня есть многопоточное приложение C ++.

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

Что если, однако, вместо проверки состояния переменной я вызываю метод, который возвращает значение переменной?Например:

static int num = 0;

...

void foo()
{
   while(getNum() == 0)
   {
      // do something (or nothing)
   }
}

Должен ли я сделать num переменной переменной?или компилятор признает, что, поскольку я использую метод для доступа к этой переменной num, он не будет кэшировать результат?

У кого-нибудь есть идеи?

Заранее спасибо,

~ Джулиан

edit : внутри цикла while я удалил вызов сна и заменил его чем-то общим, например комментарием, чтобы что-то сделать (или ничего)

Ответы [ 4 ]

4 голосов
/ 06 сентября 2010

Нет, volatile никогда не требуется, если вы выполняете необходимую синхронизацию.

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

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

Что касается конкретного вопроса, то, скрывает ли функция доступ от оптимизации, сильно зависит от реализации и ситуации. Лучше всего скомпилировать функцию получения в отдельном вызове компилятора, но даже в этом случае невозможно гарантировать, что межпроцедурная оптимизация не произойдет. Например, некоторые платформы могут помещать ИК-код в файлы .o и выполнять генерацию кода на этапе «компоновщика».

Отказ от ответственности.

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

1: sleep или пустой занятый цикл не являются «необходимой синхронизацией». Это не правильный способ написания многопоточной программы, точка. Так что в таких случаях может потребоваться изменчивость.

2: Да, sleep может не учитываться реализацией функции ввода / вывода и даже может быть помечено как чистое и без побочных эффектов. В этом случае, volatile на глобальном будет необходимо. Однако я сомневаюсь, что какие-либо реализации действительно были распространены, что могло бы нарушить циклы sleep, так как они, к сожалению, распространены.

2 голосов
/ 06 сентября 2010

То, что вы предлагаете, - это, по сути, неправильное использование «volatile», его реальная цель - сообщить компилятору, что переменная может быть изменена внешним оборудованием или другим процессом в системе, поэтому она должна каждый раз действительно считываться из памяти. его использовали.

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

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

num && x'00000001' 
0 голосов
/ 02 октября 2010

К сожалению, изменчивая семантика довольно слабая. Концепция volatile на самом деле не предназначена для использования в потоках.

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

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

В corensic мы добавили встроенную функцию в jinx.h, которая делает это более прямым способом. Примерно так:

 inline void memory_barrier() { asm volatile("nop" ::: "memory"); }

Это довольно тонко, но эффективно говорит компилятору (gcc), что он не может избавиться от этого куска непрозрачного asm и что opaque asm может читать или записывать любой глобально видимый фрагмент памяти. Это эффективно останавливает компилятор от переупорядочения нагрузок / хранилищ через эту границу.

Для вашего примера:

memory_barrier (); while (num == 0) { memory_barrier (); ... }

Теперь чтение num застряло на месте. И, что еще важнее, он застрял на месте по отношению к другому коду. Таким образом, вы могли бы иметь:

 while (flag == 0) { memory_barrier(); }  // spin
 process data[0..N]

И другой поток делает:

 populate data[0..N]
 memory_barrier();
 flag = 1;

PS. Если вы делаете такие вещи (по сути, создавая свои собственные примитивы синхронизации), выигрыш может быть большим, но риск для качества велик. Jinx особенно хорош в обнаружении ошибок в этих структурах без блокировки. Поэтому вы можете использовать его или какой-либо другой инструмент для проверки этого материала.

PPS. У сообщества linux есть хороший пост об этом, который называется «volatile считается вредным», зацените его.

0 голосов
/ 06 сентября 2010

технически это должно быть помечено как изменчивое.Компиляторы могут делать то, что хотят, для оптимизации кода, если он продолжает соответствовать спецификации абстрактной машины c ++.Соответствующий компилятор, с достаточным количеством ресурсов, может встроить все экземпляры getNum, переместить значение num в регистр (или просто, заметив, что оно никогда не изменяется каким-либо кодом, обрабатывать его как константу) в течение всего времени жизни программы.

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

...