Введение
В вычислительной технике барьер памяти, также известный как membar, memory fence или fence instruction, является типом барьерной инструкции, которая заставляет центральный процессор (CPU) или компилятор обеспечивать определенный порядок выполнения операций с памятью, выпущенных до и после этой инструкции. Обычно это означает, что операции, выполненные до барьера, гарантированно завершатся до операций, выполненных после него. Барьеры памяти необходимы, поскольку большинство современных процессоров используют оптимизации производительности, которые могут приводить к внеочередному выполнению инструкций. Это изменение порядка операций с памятью (чтений и записей) обычно не замечается в рамках одной нити выполнения, но может вызывать непредсказуемое поведение в многопоточных программах и драйверах устройств, если не контролируется должным образом. Конкретный характер ограничения порядка выполнения зависит от аппаратной реализации и определяется моделью упорядочения памяти архитектуры. Некоторые архитектуры предоставляют несколько типов барьеров для обеспечения различных ограничений порядка. Барьеры памяти обычно используются при реализации низкоуровневого машинного кода, работающего с памятью, совместно используемой несколькими устройствами. Такой код включает в себя примитивы синхронизации и безблокировочные структуры данных в многопроцессорных системах, а также драйверы устройств, взаимодействующие с компьютерным оборудованием.
In computing, a memory barrier, also known as a membar, memory fence or fence instruction, is a type of barrier instruction that causes a central processing unit (CPU) or compiler to enforce an ordering constraint on memory operations issued before and after the barrier instruction. This typically means that operations issued prior to the barrier are guaranteed to be performed before operations issued after the barrier. Memory barriers are necessary because most modern CPUs employ performance optimizations that can result in out of order execution. This reordering of memory operations (loads and stores) normally goes unnoticed within a single thread of execution, but can cause unpredictable behavior in concurrent programs and device drivers unless carefully controlled. The exact nature of an ordering constraint is hardware dependent and defined by the architecture's memory ordering model. Some architectures provide multiple barriers for enforcing different ordering constraints. Memory barriers are typically used when implementing low level machine code that operates on memory shared by multiple devices. Such code includes synchronization primitives and lock free data structures on multiprocessor systems, and device drivers that communicate with computer hardware.
Многопоточное программирование и видимость памяти
Многопоточные программы обычно используют примитивы синхронизации, предоставляемые средой программирования высокого уровня, такой как Java или .NET, или интерфейсом прикладного программирования (API), например POSIX Threads или Windows API. Примитивы синхронизации, такие как мьютексы и семафоры, используются для синхронизации доступа к ресурсам из параллельных потоков выполнения. Эти примитивы обычно реализуются с использованием барьеров памяти, необходимых для обеспечения требуемой семантики видимости памяти. В таких средах явное использование барьеров памяти, как правило, не требуется.
Оптимизация выполнения в неуместном порядке по сравнению с оптимизацией переустройства компилятора
Инструкции по барьерам памяти устраняют эффекты переупорядочивания только на аппаратном уровне. Компиляторы также могут переупорядочивать инструкции в рамках процесса оптимизации программы. Хотя влияние на поведение параллельной программы может быть схожим в обоих случаях, в целом необходимо принимать отдельные меры для предотвращения оптимизации переупорядочивания компилятором для данных, которые могут совместно использоваться несколькими потоками выполнения. В C и C++ ключевое слово `volatile` изначально предназначалось для обеспечения прямого доступа программ C и C++ к устройствам ввода-вывода, отображенным в память. Ввод-вывод, отображенный в память, обычно требует, чтобы чтения и записи, указанные в исходном коде, выполнялись в точном порядке, без пропусков. Пропуски или переупорядочивание чтений и записей компилятором нарушат связь между программой и устройством, к которому осуществляется доступ через ввод-вывод, отображенный в память. Компилятор C или C++ не должен исключать чтения и записи из областей волатильной памяти, а также не должен переупорядочивать чтения/записи относительно других таких операций для одной и той же области волатильной памяти (переменной). Ключевое слово `volatile` не гарантирует наличие барьера памяти для обеспечения согласованности кэша. Поэтому использование `volatile` само по себе недостаточно для использования переменной для межпоточной коммуникации на всех системах и процессорах. Стандарты C и C++ до C11 и C++11 не рассматривают многопоточность (или многопроцессорность), и, следовательно, полезность `volatile` зависит от компилятора и аппаратного обеспечения. Хотя `volatile` гарантирует, что волатильные чтения и волатильные записи будут выполняться в точном порядке, указанном в исходном коде, компилятор может генерировать код (или процессор может переупорядочить выполнение) таким образом, что волатильное чтение или запись будут переупорядочены относительно неволатильных чтений или записей, что ограничивает его полезность в качестве межпотокового флага или мьютекса.