Модель памяти в JavaScript 🧠
На первый взгляд с SharedArrayBuffer (общий кусок памяти между потоками, например воркерами) и Atomics (атомарные чтение/запись и синхронизация по этой памяти) всё должно идти по очереди и предсказуемо.
Спецификация здесь честная и довольно жёсткая. Если в коде нет гонки данных (data race), поведение выглядит как последовательная согласованность (sequential consistency). Как будто действия потоков аккуратно перемешали в одну линию.
Если гонка есть, начинаются странности. Могут появляться странные чтения и эффекты, которые выглядят как нарушение причинно-следственной связи. Это не магия JavaScript. Это последствия того, как оптимизируют компиляторы и как работает железо: внеочередное выполнение (out-of-order execution) и спекулятивное выполнение (speculation). Внеочередное - это когда процессор для скорости меняет порядок независимых шагов, а не идёт строго строка за строкой. Спекулятивное - это когда он заранее делает «предположительно нужные» шаги до того, как точно понял, понадобятся они или нет.
❕Самое важное: в этой части ECMAScript нет неопределённого поведения. То есть это не «делайте что хотите, результат случайный». Спека задаёт ограничения на события чтения/записи и явно определяет, какие комбинации допустимы, а какие нет❕
И ещё важный нюанс: это аксиоматическая модель, а не пошаговый алгоритм «как исполнять». Она работает через граф событий и набор ограничений: такой граф допустим, такой нет.
По-простому, атомарные доступы в модели памяти идут в общем строгом порядке для всех агентов (seq-cst, то есть единый порядок событий, который все видят одинаково), а неатомарные считаются неупорядоченными (unordered, то есть такого общего порядка для всех уже нет). ReadModifyWrite (операции «прочитал-изменил-записал» как одно действие, например Atomics.add/compareExchange) по спецификации всегда seq-cst. Отдельно важно: промежуточные режимы упорядочивания (слабее seq-cst и сильнее unordered) в этой модели не поддерживаются.
В самой модели события чтения/записи описываются полями вроде порядка, диапазона байт и полезной нагрузки. Плюс есть отдельные события синхронизации и события среды (например, передача SharedArrayBuffer через postMessage), которые тоже влияют на допустимый порядок.
Именно поэтому Atomics нужен не «для галочки». Это способ убрать гонки и получить предсказуемое поведение в многопоточке.
🌱 Итого: проблема обычно не в языке, а в гонках данных. Когда гонок нет, поведение предсказуемое и читаемое. Когда гонки есть, появляются неинтуитивные эффекты, но это не случайный хаос: модель памяти в спецификации задаёт чёткие границы допустимого.
ECMAScript, раздел 29 (Memory Model), пункт 29.1 (Memory Model Fundamentals) и таблицы событий ReadSharedMemory / WriteSharedMemory / ReadModifyWriteSharedMemory. #js
· 22.03
А на каком проводе ты?
ответить
коммент удалён