В информатике , замок или мьютекс (от взаимного исключения ) является примитив синхронизации : механизм , который накладывает ограничения на доступ к ресурсу , когда существует множество потоков выполнения . Блокировка предназначена для обеспечения соблюдения политики управления параллелизмом взаимного исключения , и с помощью множества возможных методов существует множество уникальных реализаций для разных приложений.
Как правило, блокировки являются рекомендательными блокировками , при которых каждый поток взаимодействует, получая блокировку перед доступом к соответствующим данным. Некоторые системы также реализуют обязательные блокировки , когда попытка неавторизованного доступа к заблокированному ресурсу вызовет исключение в объекте, пытающемся сделать доступ.
Самый простой тип блокировки - это двоичный семафор . Он обеспечивает эксклюзивный доступ к заблокированным данным. Другие схемы также предоставляют общий доступ для чтения данных. Другие широко применяемые режимы доступа - эксклюзивный, с намерением исключить и с намерением обновить.
Другой способ классификации блокировок - это то, что происходит, когда стратегия блокировки предотвращает продвижение потока. Большинство конструкций замка блокируют на выполнение в потоке запрашивающего блокировки , пока не будет разрешен доступ к заблокированному ресурсу. При спин-блокировке поток просто ждет («вращается»), пока блокировка не станет доступной. Это эффективно, если потоки блокируются на короткое время, поскольку позволяет избежать накладных расходов на перепланирование процессов операционной системы. Это неэффективно, если блокировка удерживается в течение длительного времени или если выполнение потока, удерживающего блокировку, зависит от вытеснения заблокированного потока.
Блокировки обычно требуют аппаратной поддержки для эффективной реализации. Эта поддержка обычно принимает форму одной или нескольких атомарных инструкций, таких как « test-and-set », « fetch-and-add » или « compare-and-swap ». Эти инструкции позволяют одному процессу проверить, свободна ли блокировка, и, если она свободна, получить блокировку за одну атомарную операцию.
В однопроцессорных архитектурах есть возможность использовать непрерывные последовательности инструкций - с использованием специальных инструкций или префиксов инструкций для временного отключения прерываний - но этот метод не работает для многопроцессорных машин с общей памятью. Правильная поддержка блокировок в многопроцессорной среде может потребовать довольно сложной аппаратной или программной поддержки со значительными проблемами синхронизации .
Причина, по которой требуется атомарная операция , связана с параллелизмом, когда несколько задач выполняют одну и ту же логику. Например, рассмотрим следующий код C :
if ( lock == 0 ) { // блокировка свободна, установите ее lock = myPID ; }
Приведенный выше пример не гарантирует, что у задачи есть блокировка, поскольку несколько задач могут тестировать блокировку одновременно. Поскольку обе задачи обнаруживают, что блокировка свободна, обе задачи будут пытаться установить блокировку, не зная, что другая задача также устанавливает блокировку. Алгоритмы Деккера или Петерсона являются возможными заменами, если операции атомарной блокировки недоступны.
Неосторожное использование блокировок может привести к тупиковой или временной блокировке . Чтобы избежать взаимоблокировок или живых блокировок или выйти из них, можно использовать ряд стратегий как во время разработки, так и во время выполнения . (Наиболее распространенная стратегия - стандартизировать последовательности получения блокировок, чтобы комбинации взаимозависимых блокировок всегда регистрировались в специально определенном «каскадном» порядке.)
Некоторые языки поддерживают синтаксические блокировки. Пример на C # следующий:
public class Account // Это монитор аккаунта { private decimal _balance = 0 ; закрытый объект _balanceLock = новый объект (); public void Deposit ( decimal amount ) { // Только один поток одновременно может выполнять этот оператор. блокировка ( _balanceLock ) { _balance + = сумма ; } } public void Withdraw ( десятичная сумма ) { // Только один поток одновременно может выполнять этот оператор. lock ( _balanceLock ) { _balance - = сумма ; } } }
Код lock(this)
может привести к проблемам, если к экземпляру можно будет получить доступ публично. [1]
Подобно Java , C # также может синхронизировать целые методы с помощью атрибута MethodImplOptions.Synchronized. [2] [3]
[MethodImpl (MethodImplOptions.Synchronized)] public void SomeMethod () { // что-то делать }
Прежде чем познакомиться с гранулярностью блокировок, необходимо понять три концепции блокировок:
При выборе количества блокировок для синхронизации существует компромисс между уменьшением накладных расходов на блокировку и уменьшением числа конфликтов блокировок.
Важным свойством замка является его гранулярность . Детализация - это мера объема данных, которые защищает блокировка. В общем, выбор грубой степени детализации (небольшое количество блокировок, каждая из которых защищает большой сегмент данных) приводит к меньшим накладным расходам на блокировку, когда один процесс обращается к защищенным данным, но к снижению производительности, когда несколько процессов работают одновременно. Это связано с увеличением числа конфликтов блокировок.. Чем грубее блокировка, тем выше вероятность того, что блокировка остановит выполнение несвязанного процесса. И наоборот, использование тонкой гранулярности (большее количество блокировок, каждая из которых защищает довольно небольшой объем данных) увеличивает накладные расходы самих блокировок, но снижает конкуренцию блокировок. Детализированная блокировка, когда каждый процесс должен удерживать несколько блокировок из общего набора блокировок, может создавать тонкие зависимости блокировок. Эта тонкость может увеличить вероятность того, что программист неосознанно зайдет в тупик . [ необходима цитата ]
В системе управления базой данных , например, блокировка может защищать в порядке уменьшения гранулярности часть поля, поле, запись, страницу данных или всю таблицу. Грубая детализация, такая как использование блокировок таблиц, обычно дает лучшую производительность для одного пользователя, тогда как мелкая детализация, такая как блокировки записей, имеет тенденцию обеспечивать лучшую производительность для нескольких пользователей.
Блокировки базы данных могут использоваться как средство обеспечения синхронности транзакций. т.е. при одновременной обработке транзакций (чередование транзакций) использование двухэтапных блокировок гарантирует, что параллельное выполнение транзакции оказывается эквивалентным некоторому последовательному упорядочиванию транзакции. Однако взаимоблокировки становятся нежелательным побочным эффектом блокировки в базах данных. Взаимоблокировки либо предотвращаются путем предварительного определения порядка блокировки между транзакциями, либо обнаруживаются с помощью графиков ожидания . Альтернатива блокировке для синхронности базы данных, позволяющая избежать взаимоблокировок, включает использование полностью упорядоченных глобальных меток времени.
Существуют механизмы, используемые для управления действиями нескольких одновременных пользователей в базе данных - цель состоит в том, чтобы предотвратить потерю обновлений и грязное чтение. Два типа блокировки - это пессимистическая блокировка и оптимистическая блокировка :
Защита ресурсов на основе блокировок и синхронизация потоков / процессов имеют много недостатков:
Некоторые стратегии управления параллелизмом позволяют избежать некоторых или всех этих проблем. Например, с помощью воронки или сериализации токенов можно избежать самой большой проблемы: тупиковых ситуаций. Альтернативы блокировке включают неблокирующие методы синхронизации , такие как методы программирования без блокировки и транзакционная память . Однако такие альтернативные методы часто требуют, чтобы фактические механизмы блокировки были реализованы на более фундаментальном уровне операционного программного обеспечения. Следовательно, они могут только освободить уровень приложения от деталей реализации блокировок, при этом проблемы, перечисленные выше, по-прежнему необходимо решать в рамках приложения.
В большинстве случаев правильная блокировка зависит от ЦП, обеспечивающего метод синхронизации потока атомарных инструкций (например, добавление или удаление элемента в конвейер требует, чтобы все одновременные операции, требующие добавления или удаления других элементов в конвейере, были приостановлены во время манипулирование содержимым памяти, необходимое для добавления или удаления конкретного элемента). Следовательно, приложение часто может быть более надежным, если оно распознает бремя, которое оно возлагает на операционную систему, и способно любезно распознавать сообщения о невыполнимых требованиях. [ необходима цитата ]
Одна из самых больших проблем программирования на основе блокировок заключается в том, что «блокировки не составляют »: трудно объединить небольшие правильные модули на основе блокировок в одинаково правильные более крупные программы, не изменяя модули или, по крайней мере, не зная об их внутреннем устройстве. Саймон Пейтон Джонс (сторонник программной транзакционной памяти ) приводит следующий пример банковского приложения: [5] разработать класс Account, который позволяет нескольким одновременным клиентам вносить или снимать деньги на счет; и дать алгоритм перевода денег с одного счета на другой. Основанное на блокировке решение первой части проблемы:
класс Учетная запись: баланс участника : Целочисленный член мьютекса: Блокировка метод deposit (n: целое число) mutex.lock () баланс ← баланс + n mutex.unlock () метод вывода (n: целое число) депозит (−n)
Вторая часть проблемы намного сложнее. Процедура передачи , правильная для последовательных программ , будет
передача функции (от: Аккаунт, в: Аккаунт, сумма: целое число) from.withdraw (сумма) to.deposit (сумма)
В параллельной программе этот алгоритм неверен, потому что, когда один поток находится на полпути к передаче , другой может наблюдать состояние, когда сумма была снята с первого счета, но еще не переведена на другой счет: деньги пропали из системы. Эта проблема может быть решена полностью только путем блокировки обеих учетных записей перед изменением любой из двух учетных записей, но тогда блокировки должны быть приняты в соответствии с некоторым произвольным глобальным порядком, чтобы предотвратить взаимоблокировку:
передача функции (от: Account, to: Account, amount: integer) if from <to // произвольный порядок блокировок from.lock () запереть() еще запереть() from.lock () from.withdraw (сумма) to.deposit (сумма) from.unlock () отпирать()
Это решение усложняется, когда задействовано больше блокировок, и передаточная функция должна знать обо всех блокировках, чтобы их нельзя было скрыть .
Языки программирования различаются по поддержке синхронизации:
synchronize
C и C ++ могут легко получить доступ к любым встроенным функциям блокировки операционной системы.lock
ключевое слово в потоке, чтобы гарантировать его монопольный доступ к ресурсу.SyncLock
ключевое слово, как ключевое слово C # lock
.synchronized
для блокировки блоков кода, методов или объектов [10] и библиотек, содержащих структуры данных, безопасные для параллелизма.@synchronized
[11] для блокировки блоков кода, а также предоставляет классы NSLock, [12] NSRecursiveLock, [13] и NSConditionLock [14] вместе с протоколом NSLocking [15] для блокировки.Mutex
класс в pthreads
расширении. [17]Lock
классом из threading
модуля. [18]lock_type
производный тип во встроенном модуле iso_fortran_env
и операторах lock
/, unlock
начиная с Fortran 2008 . [19]Mutex<T>
[21] . [22]LOCK
префикс для определенных операций, чтобы гарантировать их атомарность.MVar
, которая может быть пустой или содержать значение, обычно ссылку на ресурс. Поток, который хочет использовать ресурс, «берет» значение MVar
, оставляя его пустым, и возвращает его по завершении. Попытка взять ресурс из пустого MVar
приводит к блокировке потока до тех пор, пока ресурс не станет доступным. [23] В качестве альтернативы блокировке также существует реализация программной транзакционной памяти . [24]Защищенный объект обеспечивает скоординированный доступ к совместно используемым данным посредством вызовов его видимых защищенных операций, которые могут быть защищенными подпрограммами или защищенными записями.