#java #многопоточность #volatile
Здравствуйте, вот возник такой вопрос. Насколько я понял, переменные Volatile обязаны
быть постоянно записаны в память. А если обычная переменная, то она, как правило, хранится
в кеше процессора для более быстрого доступа. Собственно при использовании Volatile
записывается напрямую в оперативку.
Есть, скажем, какой-то процессор на 4 ядра. Соответственно 4 независимых процессора,
у каждого из которых свои кеши, например, первого и второго уровня, а третий общий.
Ну, собственно, запустили несколько потоков. Допустим, каждый из них работает на отдельном
ядре, у которого свой кеш. Делаем переменную Volatile, и она пишется напрямую в память.
А теперь если запустить программу на одноядерном процессоре, то ОС имитирует параллельность
потоков. Выходит, что кеш у процессора один и все потоки работают на одном процессоре.
Вопрос вот в чем. Переменная обычная кешируется на уровне процессора или на уровне
программном? То есть если даже одно ядро и запущенно несколько потоков, то они делят
кеши процессора на независимые области памяти, имитируя многоядерность. Или же они
используют один и тот же кеш вместе, значит, переменная Volatile не имеет смысла?
Объясните, пожалуйста, как все происходит. Спасибо.
EDIT
Я имел ввиду, что переменная для быстрого доступа не пишется сразу в память, например,
x=1 не будет сразу записана в память, так как на это нужно много тактов процессора,
а будет по возможности записана в кеш процессора, а уже потом записана в память. Если
не поставить volatile и если потоки будут одновременно менять ее, то новое значение
будет сохраняться в кеш, а не напрямую в память писаться. Если потоки работают на нескольких
ядрах, то все понятно, у каждого ядра свой кеш, и если попытаться прочитать, и при
этом последнее значение хранится в каком-то кеше, то получим не то, что надо, если,
конечно, не из того потока читать, который последний записывал. А вот если на одном
ядре, то кеш ведь один и все потоки туда пишут, тогда смысл от volatile? Или я что-то
не так понимаю.
Ответы
Ответ 1
То есть при попытке чтения если много потоков ее изменили, то у каждого своя кешированная
и при попытке прочитать будет не то что надо.
При чтении каждый процессор будет видеть локальную копию "переменной в памяти" (из
своего кеша), а вот при записи в такую переменную, он сообщит остальным процессорам,
что те должны обновить свои кеши в заданной кеш-линии перед последующим чтением.
Обычно, такие отношения дают свойство когерентности кэша. Есть процессоры, которые
обладают таким свойством, а есть и такие, для которых требуется адаптировать код, чтобы
сохранить целостность данных. И одним volatile тут не всегда можно обойтись (на ARM'ах,
например).
p.s.: только это, опять же, к volatile не имеет отношения. Грубо говоря, это свойство
разделяемой памяти SMP-архитектуры.
в этой архитектуре, нет места кэшам и проч. занудствам о которых вы пишете с таким
завидным упорством
@Barmaley, я вот еще немного поразмышлял на тему "нет места кэшам и проч. занудствам"
и все таки прихожу к выводу, что без этих знаний Вы врят ли даже на java можете расчитывать
на адекватные характеристики производительности Вашего кода для SMP. Вот пример из
жизни, с массивами.
Но когда я искал этот материал я вообще-то думал даже не о каких-то особенностях
строения массивов, а о банальной параллельной обработке классического массива: делим
массив на n частей и обрабатываем каждую в своем потоке (с какой-то долей записи в
массив, конечно же, т.е., например: "параллельное заполнение данных").
Если Вы не подумаете в рамках этой, простой на первый взгляд, задачи о возможности
вытеснения кеш-линии соседним процессором, и порождаемом при этом кеш-промахе, то не
получите для нее адекватных результатов (насколько я знаю, java пока не умеет читать
мысли программиста и добавлять padding'и под границу кеш-линии для массивов, которые
он решит распараллелить таким образом).
Ответ 2
У volatile есть один плюс - он требует, что бы значение переменной всегда читалось
с памяти. А в эту память может писать не только процессор...
Или второй вариант - есть jni код, который модифицирует переменную, а java код только
читает. И чтобы оптимизатор не выкинул чтение, нужно дописать volatile.
Ответ 3
Вам нужно разбить поток мыслей на вопросы. Пока простой ответ который можно дать
- volatile не связан с кешем.
Ну я имею ввиду обычная переменная ведь в кеше хранится для быстрого доступа?
Нет. Правильно думать о кеше и памяти, как о едином месте. Если что-то есть в кеше,
оно есть и в памяти (+- пару исключений). В большинстве современных ширпотребовских
системах кеш неуправляем, поэтому про него можно просто "забыть". (Если вы программист
высоких материй, то можно и не вспоминать).
А как на одноядерном происходит если кеш у процессора один по сути все потоки пишут
туда, тогда смысл от volatile?
Volatile - это высокоуровневый примитив, на котором в java основана MemoryModel,
он нужен на любом количестве процессоров, ядер и потоков. (Размер кеша, его количество,
иерархичность не влияют.)
Ответ 4
@Alexandr Crospov, для ответа на свои вопросы (они глубже, чем уровень Java) поразбирайтесь
с преобразованием виртуальных адресов (именно ими оперирует CPU) в физические (ключевое
слово -- MMU),
а потом с когерентностью кэшей (начните с ссылки в ответе @mega).
--
Кстати, когда говорят о потоках, то обычно подразумевают, что все потоки находятся
в одном пространстве виртуальных адресов, а вот у каждого процесса (в нем может быть
несколько потоков) свое виртуальное адресное пространство.
Ответ 5
Молодой человек - современное программирование построено на т.н. архитектуре Фон-Неймана
- почитайте про это.
Там, в этой архитектуре, нет места кэшам и проч. занудствам о которых вы пишете с
таким завидным упорством. Место хранения переменной зависит от конкретной реализации
конкретного компилятора - так что не забивайте себе голову.