Затраты на обеспечение видимости памяти в мьютексе незначительны, фактически на x86 это бесплатно.
Для получения мьютекса требуется операция atomi c чтения-изменения-записи с семантикой получения. Для освобождения мьютекса достаточно использовать простое хранилище с семантикой выпуска. Рассмотрим простую спин-блокировку - операция получения состоит из al oop, который неоднократно пытается установить флаг блокировки в 1, если он в настоящий момент равен 0. Чтобы снять блокировку, поток-владелец просто записывает 0 во флаг блокировки. Во многих отношениях такая простая спин-блокировка далека от оптимальной, и существует множество конструкций для блокировок, которые пытаются это улучшить (например, справедливость, вращение в строках локального кэша и т. Д. c.), Но во всех этих конструкциях освобождение блокировка, безусловно, дешевле, чем ее приобретение.
Модель памяти x86 довольно сильна: все операции чтения-изменения-записи atomi c последовательно согласованы, все операции хранилища эффективно освобождаются, а все операции загрузки усвоить семантику. Вот почему на x86 выпуск мьютекса может быть выполнен с помощью обычного хранилища, никаких дополнительных инструкций для обеспечения видимости эффектов памяти не требуется. На архитектурах с более слабыми моделями памяти, такими как ARM или Power, вам действительно нужны дополнительные инструкции, но их стоимость ничтожна по сравнению со стоимостью операции получения. x86 также имеет специальные барьерные инструкции, но они обычно актуальны только в определенных случаях при программировании без блокировок, и стоимость этих инструкций примерно такая же, как у некоторых atomi c чтение-изменение записи.
реальная стоимость мьютекса - это не видимость эффектов памяти, а конкуренция и сериализация выполнения. Если количество потоков, конкурирующих за мьютекс, невелико, и продолжительность, в течение которой поток удерживает мьютекс, также мала, то общее влияние на производительность также будет низким. Но если количество потоков, борющихся за мьютекс, велико и продолжительность, в течение которой поток удерживает мьютекс, также велика, то другим потокам придется ждать дольше, пока они, наконец, не смогут получить мьютекс и продолжить выполнение. Это сокращает объем работы, который может быть выполнен в течение заданного периода времени.
Я не уверен, что вы имеете в виду под «теоретически возможна блокировка без эффектов памяти?». Вся цель мьютекса - позволить выполнять некоторые операции, а также наблюдать за ними, как если бы они были atomi c. Это означает, что результат операции становится видимым для следующего владельца мьютекса. Именно это и гарантирует отношение "происходит до". Если поток A получает мьютекс, и эта операция получения происходит - после операции освобождения некоторым потоком B , то из-за транзитивности отношения «происходит до» операции выполняются by B при удерживании мьютекса должно было произойти до того, как операция A вот-вот будет выполняться - и это означает, что все эффекты памяти должны быть видны . Если это не гарантировано, то ваш мьютекс сломан, и у вас есть состояние гонки.
Что касается изменчивой переменной в вашем примере - модель памяти Java требует, чтобы все операции с совместно используемыми изменчивыми переменными были последовательно согласованы. Однако, если x доступен только внутри критического раздела (т. Е. Защищен каким-либо мьютексом), то он не обязательно должен быть изменчивым. Volatile требуется только в том случае, если некоторые потоки обращаются к переменной без каких-либо других механизмов синхронизации, таких как мьютекс.
Семантика освобождения / получения операций мьютекса необходима для упорядочивания операций внутри мьютекс. В C ++ можно реализовать мьютекс, используя расслабленные операции. Операции блокировки / разблокировки на самом мьютексе по-прежнему будут полностью упорядочены (из-за порядка модификации мьютекса), но мы потеряем отношение «происходит до», поэтому операции внутри мьютекса будут неупорядоченный . Хотя это было бы возможно в C ++, это было бы довольно абсурдно, потому что, как я пытался объяснить, сделать видимыми эффекты памяти очень дешево (на x86 это бесплатно ), но вы потеряете свойство, которое абсолютно важно практически во всех случаях. Примечание: операция сохранения для освобождения мьютекса на дешевле , чем сохранение в изменчивую переменную. Изменчивые переменные последовательно согласованы, но освобождение мьютекса может быть выполнено с помощью хранилища релизов. (Конечно, модель памяти Java не такая гибкая, как модель C ++, поэтому вы не можете реализовать ручную блокировку, используя более расслабленные операции получения / освобождения).