Thu 05:06
@avp, да, на Intel/AMD, обычно используют более/менее когерентные (синхронные) системы кэш, точнее говоря, раньше использовали, сейчас допускается асинхронность StoreLoad, плюс возможность спекулятивных вычислений. А на IBM/Sparc/ARM ещё с прошлого века используют иные, более масштабируемые, схемы кэш. Ну, а абстракции памяти Java (или C/C++) определяют минимальные гарантии, которые должны обеспечить кэш (или, бывает, распределённая память), ЦП, в части, спекулятивных вычислений (барьеров) и компиляторы.
Thu 05:06
@avp, во-первых, видимое одним из потоков неопределённо долгое время, в частности, заведомо после окончания всех операций с counter (что вполне себе, на каком-нибудь Power, или даже ARM). Во-вторых, в более менее традиционной памяти, да, минимум 2, при разумных предположения о JIT/JVM, но уже существуют системы NUMA памяти, которые, в традиционном смысле, фактически состоят только из кэш памяти. В-третих, и это, по сути, главное, в модели памяти Java нет содержимого памяти, в ней определятся только правила видимости, и это практическая потребность и направление развития. 😉
Thu 05:06
@NowhereMan , "чередование переключения потоков", да, отмечу, что современные процессоры и современную память (особенно IBM/Sparc/ARM) невозможно описать в терминах "чередования", но только в терминах моделей памяти Java или C/C++.
Thu 05:06
P.P.S. "... Если в потоки добавлять println, то скорей всего эта операция в классе PrintStream будет синхронизирована, ... " то что она тяжелая или синхронизированная, это неважно, видимое значение переменной couner данного потока на данном шаге будет скопировано в стек аргументов, а там трава не расти, результаты будут предсказанные (точнее, математически доказанные). 😉 Хотя она и так не прорастёт, ибо без шансов, поскольку там несвязанные с нами объекты синхронизации, нет у них с нами нужных отношений "произошло до". 😉
Thu 05:06
P.S. В модели памяти Java не важно сколько времени прошло от последней суммы до println(), во всех случаях println() могут выполнятся много позже окончания суммирования.
Thu 05:06
@NowhereMan, результат println() зависит от его размещения в состоянии гонок - это объективный факт. Так? Так. То что, в том вопросе место println() фиксировали, просто несколько сузило рассмотрение интересных эффектов поведения гонок. Впрочем, всю его многогранность трудно полностью описать. Например, забавно, что если в конце потоков добавить оператор counter = counter; или, там result = counter;, то минимально возможный результат так же уменьшится, причём для всех рассмотренных вариантов, включая вариант синхронизации join(). 😉 Что до автора этого вопроса, будем посмотреть. 😉
Thu 05:06
@NowhereMan, а в этом вопросе нет конкретного println(). К примеру, если в оба потока добавить по println(), то в этом сценарии, один println() потока A может напечатать минимум 1, а println() потока B - минимум 2. Ну, или наоборот, при симметричном сценарии. Вот и вся разница. (Да, если как-нибудь странная JVM будет писать в несколько приёмов, то возможно будет использовать запись 0 из другого несинхронизированного потока).
Thu 05:06
@NowhereMan , там был конкретный вопрос: что выдаст println(), "произошёл после" двух join(), которые "произошли после" операторов записи (любого на выбор) шага 11 и шага 13, последний связан, либо с оператором записи шага 5, либо с любой записью потока A. Минимальная из которых произошла на шаге 6. Итого минимум - 2. Поскольку пошатывая схема дана для синхронной памяти Intel/AMD, там немного больше шагов, чем надо, можно было просто взять нужный, но результат не меняется, поток B после инкремента запишет минимум 2). При предположении, что JVM записывает результат инкремента в один приём.
Thu 05:06
Почему нет? Когда выполняется finalize()? Может никогда, так и в этом вопросе. Мы ж не знаем, зачем вопрос задан, может автор ДСЧ хочет соорудить наблюдая из третьего потока без синхронизаций? 😉
Thu 05:06
@NowhereMan , Там в вопросе есть корректный код (который я сразу не оценил) и вопрос о том что код выдаст, и start() и join(), возможно, дают тот ответ, который там есть. Если же брать два потока в вакууме, то к моменту окончания потока A, он не обязан видеть, ни то, что он сам записал на шаге 11 (т.е. 10), ни то что записал B на шаге 13 (т.е. 2), а может видеть то, что записал B на шаге 2 (т.е. 1). P.S. Весьма похоже на проблему double-locking, в общем, это разные вопросы и они имеют разные ответы.
Thu 05:06
@NowhereMan там вопрос не имеет слов "без каких либо блокировок без volatile, без synchronized.", зато там есть start() и есть join() устанавливающие отношение "произошло до" от и к тому самому третьему потоку. А здесь start() и есть join() запрещены.
 
Nov 5, 2021 11:49
@HolyBlackCat , по-любому, если считать, что это не самодеятельность clang, а направление развития стандарта, то для обеспечения "allow compiler transformations such as removal of empty loops, even when termination cannot be proven" требуется расширить описание C++2x п. 6.9.2.3 на предмет возможности сигналов, указать что это таки точно UB, и расширить "приложение C" в части описания несовместимости этого безобразия.
Nov 5, 2021 01:57
@HolyBlackCat, в "приложение C" есть много разных слов, но слов о том что программы с бесконечными циклами, внезапно, стали UB - нет.
Nov 5, 2021 01:57
@HolyBlackCat, в C++20/23 не написано прямо и недвусмысленно, что это UB, нет там в этом контексте слов UB вообще, в стандарте написано существенно иное. Я читаю так, кто-то читает иначе. Мне кажется, что это эксцесс конкретного исполнителя, время покажет. А насчёт совместимости, то открываем разделы С++20 пп. C.1...5 C++ and ISO C... и читаем.
Nov 5, 2021 01:57
@HolyBlackCat, к примеру, программа #include <unistd.h> int main(){alarm(1);for(;;){}} вообще не нарушает положений C++20 п. 6.9.2.2 Forward progress, поток завершится в течении 1 с или немного дольше, но компилятор по вашей ссылке её компилирует некорректно, время работы и код возврата отличается. (alarm(), конечно не входит в стандарт C++20, но в стандарт входят асинхронные сигналы, просто с ними пример ошибки этого компилятора получается немного длиннее)
Nov 5, 2021 01:57
@avp, по ссылке есть более менее авторитетное оценочное суждение: “Forward progress… Progress guarantee… endless recursion or endless loop (whether implemented as a for-statement or by looping goto or otherwise) has undefined behavior.”, о бесконеных рекурсиях и бесконечных циклах, как о неопределённом поведении. Конечно, в стандарте C++20 в п. 6.9.2 Multi-threaded executions and data races, п. 6.9.2.2 Forward progress, используются несколько иные слова.
Nov 5, 2021 01:57
@karmik, в С20++ глава "Forward progress" рассматривает проблему "Concurrently executing threads might prevent progress...", т.е. если в этой программе на момент входа в main() нет параллельно выполняющихся потоков, то программа не нарушает требований стандарта.
Nov 5, 2021 01:57
@HolyBlackCat, во-первых, корректные однопоточные программы C++89/98/03/11, без требований к forward progress, являются корректными программами С++14/17/20/23.
Nov 5, 2021 01:57
@HolyBlackCat, во-вторых, оценка бесконечных циклов в многопоточных программах как имеющих еопределенное поведение (undefined behavior), это личный вывод авторов cppreference, а так же мой личный вывод. В самом же C++20 есть только утверждение, что реализация для обеспечения требований "forward progress" может рассчитывать, что рано или поздно любой поток, либо завершится, либо займётся вводом/выводом, либо будет синхронизироваться с другим потоком, и тем самым разрешит выполнение других потоков. Иными словами у реализации планировщик потоком может заниматься вытесняющей многопоточность.
Nov 5, 2021 01:57
Нет, только многопочные, Вы цитируете C++20 раздел п. 6.9.2 Multi-threaded executions and data races. И требование "Forward progress", которое нарушается, применимо только к программам у которых есть, как минимум, два параллельных потока.
Nov 5, 2021 01:57
@karmik, "ничего не спрятано"? А почему написано: "Какое поведение ожидать от кода...", а не "Какое поведение ожидать от программы..."? Да и в заголовке тоже за "пустой цикл" (заметим, что в стандарте "пустой цикл" (empty loop), по-моему, не может быть бесконечным)
Nov 5, 2021 01:57
@karmik , по ссылке, на мой взгляд, изложение недостаточно строгое, а C++20 раздел п. 6.9 предъявляет требования только к многопоточных программам. Формально, а по сути проблемы с "Forward progress" могут возникать у достаточно редких реализаций многопоточности.