Volatile для согласованности кешей

95
16 декабря 2021, 04:50

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

Answer 1

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

Развею заблуждения о кеше: Протоколы когерентности кеша заботятся только о когерентности этого самого кеша. Когда гранулированность операций не соответствует гранулированности кеша, разные ядра могут не согласится о результатах такой операции. Пример: на типичной x86 машине возможен доступ к блокам из 8 байт без выравнивания, однако протокол когерентности кеша работает только с выровненными блоками по 64 байт (кеш линия); соответственно при записи в память какой-нибудь переменной volatile long long расположенной на границе двух кеш линий, разные ядра при чтении этой переменной могут получить разные правую или левую части.

Подытожу:

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

Примечание: в vc++ есть расширение, задающее семантику атомарного доступа при обращении к volatile переменным

Answer 2

Вы правы. На когерентность кэшей volatile не влияет и к threads никакого отношения не имеет.

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

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

Однако, это описание несколько упрощено для современных процессоров, которые могут исполнять инструкции out-of-order. Для решения этой проблемы служат atomic переменные, которые заставляют компилятор вставлять в код memory barriers.

READ ALSO
C++, возникнет ли неопределенное поведение в следующем случае

C++, возникнет ли неопределенное поведение в следующем случае

Подскажите, пожалуйста, возникает ли в следующем случае неопределенное поведение:

104
Почему не выводятся нужные слова?

Почему не выводятся нужные слова?

Имеется такая задача

241