Недавно спорил с преподавателем о причинах возникновения ошибок без использования ключевого слова volatile для использования одной переменной из разных потоков, работающих на разных ядрах. Одна из причин, по его мнению это то, что volatile необходим, чтобы избежать локального кэширования, я же говорил, что современные процессоры используют протоколы синхронизации, обеспечивающие когернтность кэша, кто из нас прав?
Развею заблуждения о volatile: Наличие или отсутствие volatile в общем случае ничего не говорит об обращении с переменной в плане работы кеша процессора, ровно как и не влияет на (не)корректность использования такой переменной из разных потоков. Доступ к volatile переменной - это одно из четырех базовых действий с побочными эффектами, на обязательное выполнение хотя бы одного из которых по ходу работы потока выполнения может рассчитывать компилятор. Причем семантика этого доступа отдается на откуп реализации.
Развею заблуждения о кеше: Протоколы когерентности кеша заботятся только о когерентности этого самого кеша. Когда гранулированность операций не соответствует гранулированности кеша, разные ядра могут не согласится о результатах такой операции. Пример: на типичной x86 машине возможен доступ к блокам из 8 байт без выравнивания, однако протокол когерентности кеша работает только с выровненными блоками по 64 байт (кеш линия); соответственно при записи в память какой-нибудь переменной volatile long long расположенной на границе двух кеш линий, разные ядра при чтении этой переменной могут получить разные правую или левую части.
Подытожу:
Примечание: в vc++ есть расширение, задающее семантику атомарного доступа при обращении к volatile переменным
Вы правы. На когерентность кэшей volatile не влияет и к threads никакого отношения не имеет.
Этот атрибут переменной однозначно говорит компилятору, что при каждом обращении к переменной по чтению (т.е. когда в программе вы используете текущее значение переменной) необходимо использовать машинную инструкцию чтения из памяти в регистр процессора, а при каждом ее изменении нужно записать регистр в память.
Естественно, в системах с когерентным кэшем чтение памяти в регистр и запись регистра в память проходит через кэш процессора. Если один процессор обращается к только что измененной другим процессором памяти (ее новое значение находится в локальном кэше данного процессора), то механизм когерентности обеспечит изменение локального кэша первого процессора. Т.е. он прочтет последнее измененное значение переменной.
Однако, это описание несколько упрощено для современных процессоров, которые могут исполнять инструкции out-of-order. Для решения этой проблемы служат atomic переменные, которые заставляют компилятор вставлять в код memory barriers.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Подскажите, пожалуйста, возникает ли в следующем случае неопределенное поведение:
Написала функцию, но она меняет числа посимвольно =(