|
|
|
Вопрос касательно JMM
|
|||
|---|---|---|---|
|
#18+
Пытаюсь постичь JMM. Появился вопрос по следующему примеру: Начальные значения: Код: java 1. 2. Thread 1 Thread 2 Код: java 1. 2. 3. 4. Код: java 1. 2. Вопрос: Что может быть напечатано? Ответ на вопрос примера: Ничего 0 41 42 43 Мои рассуждения: Thread1 может выполняться на отдельном процессоре, у которого есть свой кэш по памяти. volatile переменная от этого защищена, так как чтение и запись ее идет непосредственно из основной памяти, что и гарантирует happens-before на запись-чтение. Но вполе логично предположить, что Thread1 может записать сначала 41, 42, а еще и 43 (операция a=43 может быть перемещена до ready = true из-за reordering ) в этот самый кэш, а в read запишет true, но в основную память. Thread2 увидит ready равной true и перейдет к печати a. Но Thread2 может не увидеть, что Thread1 записал 41,42,43 в свой кэш и напечатает 0. В чем я не прав? ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 13.04.2014, 22:33 |
|
||
|
Вопрос касательно JMM
|
|||
|---|---|---|---|
|
#18+
1) Утверждение "волатильная переменная читается напрямую памяти" неверно. Она читается из кэша так же, как и все остальные. Просто на это чтение накладывается ограничение, что оно должно отобразить самое актуальное значение. Это значит, что, если она, например, что если она была закэширована в регистре ядра, то надо ее перечитать из кэша. Если она сидит в invalidate queue кэша, то надо дождаться, пока кэш ее инвалидирует и перечитает актуальное значение из другого кэша, и т.д.. Общая суть такова: будет прочитано самое актуальное значение. А как конкретно это будет сделано - не нашего ума дело. Но фразу "волатильная переменная не кэшируется" лучше не употреблять, так как она не верна. 2) По сути вашего вопроса. Запись волатильной переменной имеет release семантику. Это означает, что когда эта запись станет видна другим ядрам, то им так же гарантированно будет видно и a = 42. Чтение волатильной переменной имеет acquire семантику, которая гарантирует, что все инструкции после чтения увидят все то, что предшествовало волатильной записи, результат которой мы увидели при чтении. Таким образом, в момент System.out.println() мы уверены, что инструкции a = 41 и a = 42 выполнены, и видны нам. Но 41 мы не увидим, так как 42 ее перезаписало. Что касается a = 43, то мы можем увидеть ее, а можем и нет, в зависимости от того, успели мы записать ее до чтения или нет. Поэтому ответ: 42 или 43. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 13.04.2014, 23:14 |
|
||
|
Вопрос касательно JMM
|
|||
|---|---|---|---|
|
#18+
DEVcoach, Спасибо. Я смотрю, что вы хорошо в этой области разбираетесь. Может порекомендуете что-либо по JMM на русском языке (статьи, книги) ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 13.04.2014, 23:23 |
|
||
|
Вопрос касательно JMM
|
|||
|---|---|---|---|
|
#18+
Exiart , Мне в свое время долго собирать понимание всего этого по крупицам. Где-то слишком много формализма, где-то путаница в терминологии, и т.д.. В ближайшие 2 недели я представлю публике тренинг по многопоточности, где я постарался все это дело систематизировать и свести воедино. Думаю, получится достаточно неплохо. Но а если просто почитать/посмотреть, то рекуомендую, например. начать с недавнего доклада Шипилева: ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 13.04.2014, 23:36 |
|
||
|
Вопрос касательно JMM
|
|||
|---|---|---|---|
|
#18+
DEVcoachВ ближайшие 2 недели я представлю публике тренинг по многопоточности Где можно будет воспользоваться вашим трудом? ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 13.04.2014, 23:52 |
|
||
|
Вопрос касательно JMM
|
|||
|---|---|---|---|
|
#18+
ExiartГде можно будет воспользоваться вашим трудом?У меня :-) Разумеется, это будет платная услуга. Но на первом этапе стоимость будет по сути символическая. Если говорить конкретно про многопоточноть, то все знания по ней я могу разделить на 3 категории: 1) Понимание основ - JMM, happens-before, и т.д.. 2) На основе п.1 программист применяет тривиальные инструменты (synchronized, volatile, j.u.c.) - это его повседневная практика. 3) Накопив опыт по п.2 программист готов заниматься уже замысловатыми вещами, вроде хитрых неблокирующих алгоритмов, и т.д. Соответственно, нормальное развитие в этом направлении должно идти п.1 -> п.2 -> п.3. И хорошо бы, что бы их сложность возрастала так же. Основы - просто, практика - посложнее, замысловатые задачи - еще сложнее. Однако по факту дело обстоит иначе. п.2 оказывает очень простым, а п.1 достаточно сложным. Поэтому сплошь и рядом встречаются случаи, когда программист применяет инструменты многопоточного программирования, совершенно не понимая, что за ними стоит. Ведь сами по себе эти инструменты очень просты (напр, казалось бы - ну что может быть проще, чем установить модификатор volatile или final?), а понимания из сути нет. п.1 - JMM и вся ассоцированная с ней муть - состоит из набора очень простых и понятных правил. Но эти правила на удивление сложно объяснить в общем виде. Поэтому, если вы начнете читать 17-ю главу JLS, где объясняется вся суть, вы очень быстро ее закроете, так как совершенно ничего не поймете - уж очень сложно оказывается объяснить достаточно тривиальные вещи. Вот я и пытаюсь как-то это упростить для рядового слушателя, тем самым сэкономив ему время. Получится или нет - увидим. В конечном счете то точно получится, но в начале, думаю, будет непросто :-) Если интересно - отпишитесь мне на e-mail в профиле. Когда тренинг будет готов - я вам сообщу. Туда же можете просто задавать конкретные вопросы по многопоточности, с радостью отвечу. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 14.04.2014, 00:34 |
|
||
|
Вопрос касательно JMM
|
|||
|---|---|---|---|
|
#18+
DEVcoach, Немного еще раз проштудировал теорию, вернулся к вопросу про volatile в примере предположу следующие рассуждения. Thread1 спокойно выполняется, но когда он встречает release операцию ready=true где-то на низком уровне помечается про Thread1, что когда будет каким-либо из потоков встречена acquire чтение из ready, то надо будет сбросить кэш Thread1 в основную память, а другим потоком обновить свое состояние кэша из основной памяти - это и будет точка синхронизации. Тут важно, что сброс в память состояния кэша происходит НЕ в момент записи волатильной переменной. Можно тоже самое сказать и иначе. Если поток Thread2 выполняясь встречает acquire чтение из ready, то он как минимум обновит свое состояние кэша и перед этим, в случае если где-то на низком уровне будет пометка, что каким-то потоком была release операция по этой волатайл-переменной, то будет и сброс в основную память состояния этого какого-то потока. Если хотя бы примерно так все и есть, то какой возможен вывод, если release и соотвествующий ей acquire будут в одном потоке. Thread 1 Thread 2 Код: java 1. 2. 3. 4. 5. 6. Код: java 1. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 14.04.2014, 12:31 |
|
||
|
Вопрос касательно JMM
|
|||
|---|---|---|---|
|
#18+
Exiart, в целом неправильное рассуждение, нет никаких понятий поток 1, поток 2, сбросить кэш в память и тд. А есть мембары, что это такое и как они работают можно спросить в гугле, думаю вам станет все понятно. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 14.04.2014, 12:39 |
|
||
|
Вопрос касательно JMM
|
|||
|---|---|---|---|
|
#18+
Про мембары не знаю еще, ок. Кстати, не смогу не поделиться для тех, кто сейчас это читает, что скоро будет небольшая встреча и там про JMM и что такое мембары будет рассказано. http://jugmsk.timepad.ru/event/116196/ ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 14.04.2014, 12:55 |
|
||
|
Вопрос касательно JMM
|
|||
|---|---|---|---|
|
#18+
ExiartНемного еще раз проштудировал теорию, вернулся к вопросу про volatile в примере предположу следующие рассуждения. Thread1 спокойно выполняется, но когда он встречает release операцию ready=true где-то на низком уровне помечается про Thread1, что когда будет каким-либо из потоков встречена acquire чтение из ready, то надо будет сбросить кэш Thread1 в основную память, а другим потоком обновить свое состояние кэша из основной памяти - это и будет точка синхронизации. Тут важно, что сброс в память состояния кэша происходит НЕ в момент записи волатильной переменной.Последнее предложение неверно. Тут дело вот в чем. Железо у нас работает быстро не только от того, что там транзисторы несколько нанометров, но и от того, что там применяют хитрые техники и оптимизации. Применительно к потокам можно сказать следующее: 1) Пишущий поток может полениться, и не сделать изменения видимыми другим потокам; 2) Читающий поток может полениться, и не перечитать заново значение, когда мы его об этом попросим. Это "хорошая" лень, она очень сильно ускоряет программы. Но из-за нее нам приходится придумывать всякие acquire/release. Мы можем сказать пишущему потоку сделать release, тем самым заставив его не лениться, а таки сделать свои изменения видимыми другим потокам. Но этого недостаточно, так как читающий поток может не захотеть перечитать актуальное значение. Поэтому нам надо применить acquire к читающему потоку, что бы он точно перечитал значение. И вот в этом случае - они друг друга увидят. Ошибка в ваших рассуждениях заключается в утверждении "сброс в память происходит не в момент записи волатильной переменной". Во-первых, почитайте про протоколы когерентности кэшей, про MESI. Оттуда вы поймете, что процессор может не только "читать из своего кэша" или "читать из памяти", но он так же может "читать из кэша другого процессора". Поэтому сама постановка вопроса "сброс в память" - уже неверна. Во-вторых, в самой распространенной современной аритектуре - x86 - существует так называемый TSO - total store order. Этот порядок оставляет очень небольшое пространство для маневра со всякими там реордерингами. Поэтому как правило все записи достаточно быстро становятся видны другим потокам, и не особо то реордерятся. Придушите оптимизации JIT'a, и ваш код скорее всего будет нормально работать и без volatile. В-третьих, как я уже говорил ранее, рассуждать о том, что на самом деле делает железо на данном этапе - это все от лукавого. Сегодня вы работаете на одном железе на одной JVM. Они себя ведут одним образом. Завтра придумали новый крутой протокол для кэшей и мегакрутые оптимизации для JIT, и они стали вести себя иначе. И т.д.. Вы никогда толком не знаете, что там происходит в кишках. Оно вам и не надо. Поэтому думать про все это бессмысленно до тех пор, пока вы не дошли до сверхнизкого уровня, из которого хотите выжать максимальный перфоманс. А такие ситуации - доли процента от всех решаемых задач многопоточного программирования. Так что не морочьте себе голову. Release - заставил поток дать другим потокам возможность увидеть изменения. Acquire - заставил поток увидеть изменения. Точка. Не надо про "сброс в память", это ни к чему. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 14.04.2014, 20:22 |
|
||
|
Вопрос касательно JMM
|
|||
|---|---|---|---|
|
#18+
DEVcoach, Да, немного поработал над осознанием MESI ( http://habrahabr.ru/post/209128/). Далеко не продвинулся, но огромной для меня экспой стало осознание, что кэши обмениваются сообщениями, и существуют там свои очереди и далеко не все однозначно на первый взгляд. Отсюда и утверждение: "действия, выполненные одним потоком, другой поток увидит в другом порядке". И действительно, "сброс в память" ни к чему. Опять обрел силу вопрос про reordering. Боюсь опять ошибиться, но прошу поправить меня, если я не так понимаю и это. Реордеринг - для JMM не существует как такового, это всего лишь абстракция над всем тем <не знаю как назвать>, что творит JIT и ядра. Мне, как программисту, вообще не должно быть важно, в каком там порядке операции выполняются сегодня на этом процессоре, и как завтра на другом. Для меня важно другое: какое значение я могу прочитать в какой-то строчке кода, зная контекст ее выполнения. Но как я могу анализировать код, если я не представляю, как сработает этот самый reordering. Вот пример из блога http://cheremin.blogspot.ru/: Код: java 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. Может этот код вывести "va=1, a=0"? Т.е. может ли быть a = vb переставлено до va = 1? И вот чем мне нужно оперировать, чтобы ответить на этот вопрос? Пример взят отсюда http://cheremin.blogspot.ru/2012/04/lazyset-jmm.html и я так и не понял, какой ответ :( потому что вконец запутался. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 15.04.2014, 00:08 |
|
||
|
Вопрос касательно JMM
|
|||
|---|---|---|---|
|
#18+
Exiart , По когерентности кэшей и store buffer / invalidate queue классикой является вот эта статья: http://www.rdrop.com/~paulmck/scalability/paper/whymb.2009.04.05a.pdf Посмотреть, какие конкретно эффекты могут наблюдаться за счет трюков железа, можно смотреть в спецификации конкретных процессоров. Например: http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-system-programming-manual-325384.pdf глава 8.2. По условию задачки там рассматривается вариант "va=0, a=1". С точки зрения тут никаких противоречий нет, это возможно. Дальше в дебри лезть не очень хочется, поэтому и рассуждать на тему глобального порядка SO не буду. Если хотите докопаться до истины - пишите в concurrency-interest, там такое любят :-) По поводу отношения ко всему этому вы в принципе верно написали. Нам не важен реальный физический порядок выполнения инструкций. Мне важно лишь то, на результат работы каких инструкций я могу рассчитывать в конкретной точке кода. Это и есть happens-before. Например, вот у нас два потока: Вопрос: что гарантированно видно из F? Ответ: только D и Е, так как в рамках одного потока мы всегда видим результаты инструкций так, будто они выполнялись в программном порядке. То есть они могут конечно реордерится, но мы этого никогда не заметим. А вот инструкции A, B, C я не вижу из F. Но если добавить acquire/release, то все становится веселее: Теперь, если мы в E увидели какое-то значение, однозначно указывающее на то, что B завершилось, то значит между B и E существует synchronized-with. А следовательно из F мы теперь видим так же A и B. А как нам узнать, что E видно B? Вот так: Код: java 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. То есть мы сначала проверили наличие synchronized-with между B и E путем вызова if (y == 1). Если он вернул true, значит B уже выполнилась. А значит, выполнилась и A. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 15.04.2014, 00:54 |
|
||
|
|

start [/forum/topic.php?fid=59&msg=38614735&tid=2127342]: |
0ms |
get settings: |
10ms |
get forum list: |
14ms |
check forum access: |
2ms |
check topic access: |
2ms |
track hit: |
156ms |
get topic data: |
12ms |
get forum data: |
3ms |
get page messages: |
65ms |
get tp. blocked users: |
2ms |
| others: | 241ms |
| total: | 507ms |

| 0 / 0 |
