Переменные многопоточности с ++
Я пишу приложение на C++.
У меня есть переменная класса, в которую записывается несколько потоков.
В C++ все, что может быть изменено без компилятора, "понимающего", что оно изменяется, должно быть помечено как volatile, верно? Так что, если мой код многопоточный, и один поток может писать в переменную, а другой читает из нее, нужно ли отмечать переменную volaltile?
[У меня нет состояния гонки, так как я полагаюсь на записи в атомы, являющиеся атомарными]
Спасибо!
6 ответов
В C++ пока нет условий для многопоточности. На практике volatile не делает то, что вы имеете в виду (оно было разработано для оборудования, работающего с памятью, и хотя эти две проблемы схожи, они достаточно различны, так что volatile не делает правильных вещей - обратите внимание, что volatile используется в других язык для использования в Mt контекстах).
Поэтому, если вы хотите записать объект в одном потоке и прочитать его в другом, вам придется использовать функции синхронизации, которые нужны вашей реализации, когда они нужны. Для того, кого я знаю, летучие не играют никакой роли в этом.
К вашему сведению, следующий стандарт будет учитывать MT, и изменчивость не будет играть в этом никакой роли. Так что это не изменится. Вы просто будете иметь стандартные определенные условия, в которых необходима синхронизация, и стандартный определенный способ их достижения.
Да, энергозависимость - это абсолютный минимум, который вам нужен. Это гарантирует, что генератор кода не будет генерировать код, который хранит переменную в регистре и всегда выполняет чтение и запись из / в память. Большинство генераторов кода могут предоставить гарантии атомарности для переменных, которые имеют тот же размер, что и собственное слово ЦП, они обеспечат выравнивание адреса памяти, чтобы переменная не могла охватывать границу строки кэша.
Это, однако, не очень сильный контракт на современные многоядерные процессоры. Volatile не обещает, что другой поток, работающий на другом ядре, сможет видеть обновления переменной. Для этого требуется барьер памяти, обычно инструкция, которая очищает кэш процессора. Если вы не предоставите барьер, поток будет продолжать работать до тех пор, пока такой сброс не произойдет естественным образом. Это в конечном итоге произойдет, планировщик потока должен предоставить один. Это может занять миллисекунды.
После того, как вы позаботились о таких деталях, вы в конечном итоге заново изобрели условную переменную (она же событие), которая вряд ли будет быстрее, чем та, которая предоставляется библиотекой потоков. Или так же хорошо проверено. Не придумывайте свои собственные, многопоточность достаточно сложна, чтобы получить права, вам не нужно FUD, чтобы не быть уверенным, что самые простые примитивы являются надежными.
volatile указывает компилятору не оптимизировать "интуицию" значения или использования переменной, поскольку она может быть оптимизирована "извне".
Однако volatile не обеспечивает никакой синхронизации, и ваше предположение о том, что записи в int являются атомарными, практически реалистично!
Я предполагаю, что нам нужно увидеть какое-то использование, чтобы узнать, нужна ли volatile в вашем случае (или проверить поведение вашей программы) или, что более важно, если вы видите какую-то синхронизацию.
Я думаю что volatile
действительно применимо только для чтения, особенно чтения регистров ввода-вывода с отображением в памяти.
Он может использоваться для указания компилятору не предполагать, что после прочтения из области памяти значение не изменится:
while (*p)
{
// ...
}
В приведенном выше коде, если *p
не записано в цикле, компилятор может решить переместить чтение за пределы цикла, примерно так:
cached_p=*p
while (cached_p)
{
// ...
}
Если p
это указатель на порт ввода-вывода с отображением в памяти, вам нужна первая версия, в которой порт проверяется перед каждым вводом цикла.
Если p
указатель на память в многопоточном приложении, вы все еще не гарантированы, что записи являются атомарными.
Без блокировки вы все равно можете получить "невозможные" переупорядочения, выполняемые компилятором или процессором. И нет никакой гарантии, что записи в ints являются атомарными.
Было бы лучше использовать правильную блокировку.
Volatile решит вашу проблему, т.е. это гарантирует согласованность всех кэшей системы. Однако это будет неэффективно, поскольку будет обновлять переменную в памяти для каждого доступа R или W. Вместо этого вы можете использовать барьер памяти, только когда это необходимо. Если вы работаете с gcc или icc или посмотрите на встроенные модули синхронизации: http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html
РЕДАКТИРОВАТЬ (в основном о PM100 комментарий): я понимаю, что мои убеждения не являются ссылкой, поэтому я нашел что-то процитировать:)
Ключевое слово volatile было разработано, чтобы предотвратить оптимизацию компилятора, которая может сделать код некорректным при наличии определенных асинхронных событий. Например, если вы объявляете примитивную переменную как volatile, компилятору не разрешается кэшировать ее в регистре.
Интереснее:
Летучие поля линеаризуемы. Чтение изменчивого поля похоже на получение блокировки; рабочая память становится недействительной, а текущее значение изменчивого поля перечитывается из памяти. Запись изменчивого поля аналогична снятию блокировки: изменяемое поле немедленно записывается обратно в память. (это все о последовательности, а не об атомарности)
от Искусство многопроцессорного программирования, Морис Херлихи и Нир Шавит
Блокировка содержит код синхронизации памяти, если вы не блокируете, вы должны что-то сделать, и использование ключевого слова volatile, вероятно, самое простое, что вы можете сделать (даже если он был разработан для внешних устройств с памятью, привязанной к адресному пространству, это не главное Вот)