- •Типы блокировок (механизмов синхронизации)
- •Определение семафора
- •Применение семафоров
- •Проблемы семафоров
- •Мьютексы в Win32 api
- •Мьютексы в Unix-подобных системах
- •Мьютексы в языке Си
- •Простейший пример взаимной блокировки
- •Применение спинлоков
- •Проблемы спинлоков и методы их решения
- •Специфика многопроцессорных и однопроцессорных конфигураций
- •Альтернативы спинлоков
- •Прочие модификации спинлоков
Простейший пример взаимной блокировки
Шаг |
Процесс 1 |
Процесс 2 |
0 |
Хочет захватить A и B, начинает с A |
Хочет захватить A и B, начинает с B |
1 |
Захватывает ресурс A |
Захватывает ресурс B |
2 |
Ожидает освобождения ресурса B |
Ожидает освобождения ресурса A |
3 |
Взаимная блокировка |
|
Отладка взаимных блокировок, как и других ошибок синхронизации, усложняется тем, что для их возникновения нужны специфические условия одновременного выполнения нескольких процессов (в вышеописанном примере если бы процесс 1 успел захватить ресурс B до процесса 2, то ошибка не произошла бы).
Livelock
Это слово означает такую ситуацию: система не «застревает» (как в обычной взаимной блокировке), а занимается бесполезной работой, её состояние постоянно меняется — но, тем не менее, она «зациклилась», не производит никакой полезной работы.
Жизненный пример такой ситуации: двое встречаются лицом к лицу. Каждый из них пытается посторониться, но они не расходятся, а несколько секунд сдвигаются в одну и ту же сторону.
Поиск взаимных блокировок осуществляется путем построения и анализа графа ожидания. В графе ожидания узлами отмечаются процессы и объекты. Блокировки отмечаются рёбрами, направленными от узла, соответствующего захваченному объекту, к узлу, соответствующему захватившему его процессу. Ожидания отмечаются рёбрами, направленными от узла, соответствующего ожидающему процессу, к узлу, соответствующему ожидаемому объекту.
Цикл в графе ожидания соответствует взаимной блокировке. Существует специальный алгоритм поиска циклов в графе .
Существуют алгоритмы удаления взаимной блокировки. В то же время, выполнение алгоритмов поиска удаления взаимных блокировок может привести к livelock — взаимная блокировка образуется, сбрасывается, снова образуется, снова сбрасывается и так далее.
Кроме того, эти алгоритмы реализуются менеджером ресурсов — программой, отвечающей за блокировку и разблокировку. Если же часть занятых в блокировке ресурсов распределяется кем-то другим, обнаружение взаимной блокировки невозможно. К примеру, СУБД Oracle обнаруживает взаимную блокировку запросов к её базам данных, но если в приведенном примере объекты — это поле базы и, к примеру, файл на жестком диске, взаимная блокировка обнаружена не будет — СУБД этот файл не обрабатывает и для неё взаимной блокировки нет.
Практически об устранении взаимных блокировок надо заботиться ещё на этапе проектирования системы — это единственный более-менее надежный способ с ними бороться. В крайнем случае, когда основная концепция не допускает возможности избежать взаимных блокировок, следует хотя бы строить все запросы ресурсов так, чтобы такие блокировки безболезненно снимались.
Классический способ борьбы с проблемой — разработка иерархии блокировок, установление правила, что некоторые блокировки никогда не могут захватываться в состоянии, в котором уже захвачены какие-то другие блокировки. Говоря точно, речь о разработке отношения сравнения между блокировками, и о запрете захвата «большей» блокировки в состоянии, когда уже захвачена «меньшая».
В некоторых случаях, особенно в поделенных на модули архитектурах, это является проблемой. Так, например, в межмодульном интерфейсе приходится вводить вызовы, которые не делают ничего, кроме захвата и освобождения неких блокировок в модуле. Такой подход используется в файловых системах Windows в интерфейсе их взаимодействия с подсистемами кэша и виртуальной памяти.
В файловой системе существуют блокировки, «защищающие» переменные «размер файла» и «длина реально записанных данных в файле». В некоторых случаях возможно исполнение ввода-вывода на диск с удержанием этих блокировок.
Исполнение же ввода-вывода, в том числе построение запросов ввода-вывода, требует взятия блокировок низкого уровня уже в подсистеме виртуальной памяти, и следующий за этим вызов в файловую систему.
Для реализации этого шаблона файловая система предоставляет подсистеме виртуальной памяти вызовы, специально предназначенные для захвата блокировок.
Есть способы избежания данной проблемы:
Не бери ложку, если не положил вилку
Бери ложку и вилку сразу (WaitForMultipleObjects)
Для того чтобы избежать взаимной блокировки можно использовать специальные алгоритмы и методы.
Название |
Условия Коффмана |
Запатентован |
Описание |
Алгоритм Банкира |
взаимное исключение |
н/д |
Алгоритм Банкира это алгоритм распределения ресурсов и обхода взаимоблокировок разработанный Э. Дейкстра. |
Предотвращение рекурсивных блокировки |
взаимное исключение |
н/д |
Это предотвращает поток от входа в одну и ту же блокировку несколько раз. |
Спинлок (англ. Spinlock — циклическая блокировка) — низкоуровневый примитив синхронизации, применяемый в многопроцессорных системах для реализации взаимного исключения.
Физически спинлок представляет собой переменную в памяти и реализуется на атомарных операциях, которые должны присутствовать в системе команд процессора. Каждый процессор, желающий получить доступ к разделяемому ресурсу, атомарно записывает условное значение «занято» в эту переменную, используя аналог операции swap (в архитектуре x86 — xchg). Если предыдущее значение переменной (возвращаемое командой) было «свободно» то считается, что данный процессор получил доступ к ресурсу, в противном случае, процессор возвращается к операции swap и крутится в цикле ожидая, пока спинлок будет освобождён. После работы с разделяемым ресурсом процессор-владелец спинлока должен записать в него условное значение «свободно».
Пример реализации спинлока на ассемблере x86:
mov eax, spinlock_address
mov ebx, SPINLOCK_BUSY
wait_cycle:
lock xchg [eax], ebx
cmp ebx, SPINLOCK_FREE
jnz wait_cycle
;<спинлок захвачен данным процессором, работа с разделяемым ресурсом>
mov eax, spinlock_address
mov ebx, SPINLOCK_FREE
lock xchg [eax], ebx
Более интеллектуальная реализация будет использовать обычную, а не атомарную операцию, для опроса в цикле, а атомарную операцию — только для попыток захвата. Дело в том, что реализация атомарных операций с памятью происходит путем аппаратного блокирования системной шины процессором на время выполнения атомарной операции (которая включает чтение, модификацию и запись). Во время выполнения этих трёх операций выполнение каких-либо других операций на шине невозможно, что снижает производительность других процессоров в системе (если они разделяют общую шину), даже если они не имеют никакого отношения к данному спинлоку.
Также используются т. н. queued spinlocks — «спинлоки с очередью». В них вместо присвоения 0 или 1 в атомарную переменную используется атомарное добавление структуры на голову списка, при том, что голова списка есть атомарная переменная типа «указатель».
Полезные свойства queued spinlockов:
гарантия порядка предоставления в порядке запроса, гарантия от «голоданий»
в цикле опроса каждый процессор опрашивает свою локальную переменную
ровно 1 атомарная операция при захвате и ровно 1 при освобождении
Спинлоки применяются для синхронизации небольших участков кода, когда использование более сложных механизмов неоправданно или невозможно. Реализация примитивов синхронизации и диспетчера потоков обязательно требует блокировок, защищающих списки потоков, готовых к исполнению, и списки потоков, ожидающих на объектах. Такая блокировка может быть только спинлоком ввиду её очень низкого уровня. Таким образом, спинлок есть низший примитив синхронизации, на котором основана реализация всех остальных.
