переменные многопоточности с ++ - PullRequest
6 голосов
/ 26 февраля 2010

Я пишу приложение на C ++.

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

В C ++ все, что может быть изменено без «понимания» компилятором того, что оно изменяется, должно быть помечено как volatile, верно? Так что, если мой код многопоточный, и один поток может писать в переменную, а другой читает из нее, нужно ли отмечать переменную volaltile?

[У меня нет состояния гонки, так как я полагаюсь на записи в целые атомы]

Спасибо!

Ответы [ 6 ]

13 голосов
/ 26 февраля 2010

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

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

К вашему сведению, следующий стандарт будет учитывать MT, и volatile не будет в этом участвовать. Так что это не изменится. У вас просто будут стандартные условия, в которых требуется синхронизация, и стандартный способ их достижения.

4 голосов
/ 26 февраля 2010

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

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

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

3 голосов
/ 26 февраля 2010

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

volatile не обеспечит никакой синхронизации, и ваше предположение о том, что записи в int являются атомарными, практически реалистично!

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

1 голос
/ 26 февраля 2010

Я думаю, что volatile действительно относится только к чтению, особенно к чтению регистров ввода-вывода с отображением в памяти.

С его помощью можно сказать компилятору, чтобы он не предполагал, что после прочтения из ячейки памяти значение не изменится:

while (*p)
{
  // ...
}

В приведенном выше коде, если *p не записано в цикле, компилятор может решить переместить чтение за пределы цикла, что-то вроде:

cached_p=*p
while (cached_p)
{
  // ...
}

Если p - указатель на порт ввода-вывода с отображением в памяти, вам нужна первая версия, в которой порт проверяется перед каждым вводом цикла.

Если p - указатель на память в многопоточном приложении, вы все еще не гарантированы, что записи являются атомарными.

0 голосов
/ 26 февраля 2010

Volatile решит вашу проблему, т.е. это будет гарантировать согласованность всех кэшей системы. Однако это будет неэффективно, поскольку будет обновлять переменную в памяти для каждого доступа R или W. Вы можете использовать барьер памяти только тогда, когда это необходимо. Если вы работаете с gcc / icc или посмотрите встроенные функции синхронизации: http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html

РЕДАКТИРОВАТЬ (в основном около pm100 комментарий): Я понимаю, что мои убеждения не являются ссылкой, поэтому я нашел что-то процитировать:)

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

С Доктор Добб

Дальше интереснее:

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

из Искусство многопроцессорного программирования , Морис Херлихи и Нир Шавит

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

0 голосов
/ 26 февраля 2010

Без блокировки вы все равно можете получить «невозможные» переупорядочения, выполняемые компилятором или процессором. И нет никакой гарантии, что записи в ints являются атомарными.

Было бы лучше использовать правильную блокировку.

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