#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
Молодой человек - современное программирование построено на т.н. архитектуре Фон-Неймана - почитайте про это. Там, в этой архитектуре, нет места кэшам и проч. занудствам о которых вы пишете с таким завидным упорством. Место хранения переменной зависит от конкретной реализации конкретного компилятора - так что не забивайте себе голову.
Комментариев нет:
Отправить комментарий