Модели памяти языков программирования
Хорошая, хоть и длинная, статья про то, какие проблемы могут быть с точки зрения распространения изменений данных при многопоточной обработке, от создателя языка Go. Есть еще и первая часть, про те же проблемы с точки зрения процессоров, но она поскучнее и менее интересна (большая часть кратко повторена в программной части).
В хардварной части в основном рассматриваются разные ситуации с гонками и последовательной согласованностью (Sequential consistency). Раскрывается мысль, что даже на уровне железа есть приколы с многопоточностью. Получается, что очень похожие проблем с параллелизмом случаются в разных контекстах и на разных уровнях абстракций: в процессоре, языке программирования, узлах распределенной БД, микросервисах. И методы решения похожи: куча очередей (как в x86), куча синхронизирующихся копий состояния (как в ARM). Разумеется, x86 и ARM дают разные гарантии консистентности. Под конец статьи рассказывается о модели синхронизации DRF (data-race-free). Вкратце суть такая: кроме чтения и записи, для памяти есть еще операция синхронизации. Сихнронизация служит “барьером”: операции чтения и записи можно перемешивать как угодно, но нельзя перемещать через барьер. И в 1990 доказали, что если писать программу без гонок (нормально синхронизировать чтение-запись между потоками), то тогда на железо, соответствующее DRF, будет выполнять с последовательной согласованностью, т.е. как будто она вся выполнялась в одном потоке. И это очень классный результат, потому что позволил абстрагироваться от кучи проблем, написанных в начале статьи, и дать хоть какой-то простор для оптимизаций компилятору.
В программной части больше интересного. Например, Java, оказывается, первый язык, в котором была попытка прописать поведение многопоточных программ на уровне спецификации (1996). С первого раза не получилось, но в 2004 в Java 1.5 добавили фич и она стала поддерживать модель DRF, причем в этом активно участвовала одна из авторов оригинальной статьи. Отношение happens-before как реализация DRF — важная часть модели памяти Java. Однако создатели в 2010 году признали, что у этой реализации есть баги, хоть она и является хорошим компромиссом между сложностью и надежностью.
В C++11 использовали модель Java как основу (sic!), пытались сделать проще, но в итоге только усложнили и она стала менее полезной для программистов. Ну и undefined behaviour добавили, куда ж без него, и стремные атомики, которые особо ничего не гарантируют. Однако в Си, Rust и Swift использовали модель C++11 (последние два — потому что LLVM и интеграция). JavaScript пошел своим путем: его модель памяти совместима с C++11, но ближе к Java (место для вашей шутки про название языка).
Хоть автор и с оптимизмом смотрит в будущее, текущая ситуация с моделями памяти тяжелая: до сих пор не предложена модель конкурентных вычислений с примитивами для корректной быстрой синхронизации памяти, и никто до конца не понимает и не может формализовать гонки.