Операционные системы
Взаимоблокировки
Два процесса замерли, вечно ожидая друг друга. Ни один не может продолжить, система зависла. Это взаимоблокировка - одна из самых коварных проблем параллельного программирования. Как её предотвратить или обнаружить?
- **Банковские системы:** два потока одновременно переводят деньги между одними и теми же счетами в разных направлениях - оба захватывают по одному мутексу и ждут второго. Система зависает.
- **Базы данных:** транзакция A блокирует строку X и ждёт строку Y, транзакция B блокирует Y и ждёт X. СУБД должна обнаружить deadlock и откатить одну из транзакций.
- **Операционные системы:** процессы конкурируют за ресурсы (память, файлы, принтеры). ОС должна либо предотвращать deadlock через умное планирование, либо обнаруживать и разрешать их.
Цели урока
- Назвать 4 условия Coffman: mutual exclusion, hold-and-wait, no preemption, circular wait
- Прорисовать resource allocation graph и найти цикл
- Применять prevention (lock ordering), avoidance (Banker's), detection (wait-for graph в БД)
- Различать deadlock, livelock и starvation
- Распознать классику: dining philosophers, reader-writer, sleeping barber
Deadlock Conditions
**Взаимоблокировка (deadlock)** - это ситуация, когда два или более процессов бесконечно ждут ресурсы, занятые друг другом. Процессы заблокированы и не могут продолжить выполнение.
В 1971 году Эдвард Коффман сформулировал **четыре необходимых условия** для возникновения взаимоблокировки. Все четыре условия должны выполняться одновременно.
**Условия Коффмана (Coffman conditions):** 1. **Mutual Exclusion** - ресурс может использоваться только одним процессом 2. **Hold and Wait** - процесс удерживает ресурс и ждёт другие 3. **No Preemption** - ресурс нельзя отобрать принудительно 4. **Circular Wait** - существует циклическая цепочка ожидания ресурсов
Разбор каждого условия:
**1. Mutual Exclusion (взаимное исключение)** Ресурс находится в неразделяемом режиме - только один процесс может использовать его в данный момент. Примеры: принтер, файл в режиме записи, участок критической секции.
**2. Hold and Wait (удержание и ожидание)** Процесс, удерживающий хотя бы один ресурс, ждёт получения дополнительных ресурсов, удерживаемых другими процессами.
**3. No Preemption (отсутствие вытеснения)** Ресурс может быть освобождён только добровольно процессом, который его удерживает. Операционная система не может принудительно отобрать ресурс.
**4. Circular Wait (циклическое ожидание)** Существует циклическая цепочка процессов {P₀, P₁, ..., Pₙ}, где P₀ ждёт ресурс от P₁, P₁ ждёт ресурс от P₂, ..., Pₙ ждёт ресурс от P₀.
Процесс P1 захватил мутекс A, затем запросил мутекс B. Процесс P2 захватил мутекс B, затем запросил мутекс A. Какое условие Коффмана НЕ выполняется в этой ситуации?
Resource Graph
**Resource Allocation Graph (граф выделения ресурсов)** - это ориентированный граф для визуализации состояния системы и обнаружения взаимоблокировок.
**Элементы графа:** - **Процессы (P)** - круглые узлы - **Ресурсы (R)** - квадратные узлы - **Рёбра:** - **Request edge** (P → R): процесс запрашивает ресурс - **Assignment edge** (R → P): ресурс выделен процессу - Точки внутри ресурса - количество экземпляров
**Теорема о deadlock:** Если граф **не содержит циклов** - deadlock невозможен. Если граф **содержит цикл**: - Для ресурсов с **одним экземпляром** - deadlock есть - Для ресурсов с **несколькими экземплярами** - deadlock возможен (но не гарантирован)
**Алгоритм обнаружения цикла** в графе выделения ресурсов:
Граф выделения ресурсов сводит обнаружение deadlock к поиску цикла в направленном графе: DFS за O(V+E) по состоянию системы. Ядро ОС может запускать такую проверку периодически, чтобы ловить deadlock в runtime.
Дан граф: процесс P1 держит ресурс R1 и ждёт R2; процесс P2 держит R2 и ждёт R3; процесс P3 держит R3. Каждый ресурс имеет один экземпляр. Есть ли deadlock?
Deadlock Prevention
**Deadlock Prevention** - это стратегия предотвращения взаимоблокировок путём нарушения хотя бы одного из четырёх условий Коффмана.
**Методы предотвращения deadlock:** 1. **Нарушить Mutual Exclusion** - сделать ресурсы разделяемыми 2. **Нарушить Hold and Wait** - захватывать все ресурсы сразу 3. **Нарушить No Preemption** - разрешить отбирать ресурсы 4. **Нарушить Circular Wait** - упорядочить ресурсы
**1. Нарушение Mutual Exclusion** Делаем ресурсы разделяемыми, где это возможно. Подходит для read-only ресурсов.
**2. Нарушение Hold and Wait** Процесс должен запросить все нужные ресурсы одновременно перед началом выполнения.
**3. Нарушение No Preemption** Если процесс не может получить ресурс, он освобождает все удерживаемые ресурсы и пробует позже.
**4. Нарушение Circular Wait через упорядочивание ресурсов** Самый практичный метод: назначаем всем ресурсам глобальный порядок и всегда захватываем их в этом порядке.
**Banker's Algorithm (алгоритм банкира)** Более сложный метод предотвращения - система предварительно проверяет, не приведёт ли выделение ресурса к небезопасному состоянию.
**Выбор метода предотвращения:** - **Упорядочивание ресурсов** - самый практичный для большинства случаев - **RW-locks** - для read-heavy нагрузки - **Алгоритм банкира** - когда нужна максимальная безопасность (критические системы) - **tryLock + backoff** - для распределённых систем
В банковской системе потоки переводят деньги между счетами. Каждый счёт защищён мутексом. Какой метод предотвращения deadlock наиболее практичен?
Deadlock Detection
**Deadlock Detection** - это стратегия, при которой система позволяет взаимоблокировкам происходить, но периодически обнаруживает их и восстанавливает работу.
**Подход Detection + Recovery:** 1. **Detection** - периодически проверяем наличие deadlock 2. **Recovery** - если обнаружен, разрешаем его **Методы восстановления:** - Завершить один или несколько процессов - Отобрать ресурсы у процессов (preemption)
**Wait-For Graph (граф ожидания)** Для систем с одним экземпляром каждого ресурса можно упростить Resource Allocation Graph до Wait-For Graph:
**Обнаружение deadlock для ресурсов с несколькими экземплярами:** Используется алгоритм, похожий на алгоритм банкира, но работающий с текущим состоянием:
**Восстановление после обнаружения deadlock:**
**Когда запускать Detection?** Компромисс между overhead и временем реакции:
**Частота запуска Detection:** 1. **По таймеру** - каждые N секунд - Простая реализация - Может пропустить короткие deadlock 2. **По событию** - когда процесс блокируется - Быстрое обнаружение - Высокий overhead 3. **Adaptive** - чаще при высокой нагрузке - Баланс производительности - Сложнее реализовать
**Detection vs Prevention:** **Prevention:** - ➕ Deadlock никогда не происходит - ➖ Низкая утилизация ресурсов - ➖ Ограничения на порядок запросов **Detection + Recovery:** - ➕ Высокая утилизация ресурсов - ➕ Нет ограничений на запросы - ➖ Overhead на detection - ➖ Нужен механизм recovery
Deadlock можно полностью предотвратить без снижения производительности системы.
Любой метод предотвращения deadlock имеет свою цену: либо снижение утилизации ресурсов (prevention), либо overhead на обнаружение и восстановление (detection).
Это фундаментальный компромисс в проектировании систем. Prevention требует консервативного подхода (например, захват всех ресурсов сразу или строгое упорядочивание), что ограничивает параллелизм. Detection позволяет большую свободу, но требует мониторинга и механизмов восстановления. Выбор стратегии зависит от требований системы: критические системы обычно выбирают prevention, высоконагруженные - detection.
Система обнаружила deadlock между процессами P1, P2, P3. P1 выполняется 10 секунд и держит 5 ресурсов, P2 выполняется 2 секунды и держит 2 ресурса, P3 выполняется 30 секунд и держит 8 ресурсов. Какой процесс лучше выбрать жертвой?
Ключевые идеи
- **Четыре условия Коффмана:** для deadlock нужны все четыре - mutual exclusion, hold and wait, no preemption, circular wait. Нарушение хотя бы одного предотвращает deadlock.
- **Resource Allocation Graph:** визуализация состояния системы. Цикл в графе указывает на потенциальный (single instance) или возможный (multiple instances) deadlock.
- **Prevention vs Detection:** prevention ограничивает систему для гарантии отсутствия deadlock, detection позволяет большую свободу но требует механизмов обнаружения и восстановления.
- **Практичные методы:** упорядочивание ресурсов (нарушает circular wait) - самый простой и эффективный метод для большинства случаев. Алгоритм банкира - для критических систем. Detection через wait-for graph - для высоконагруженных систем.
Связанные темы
Взаимоблокировки тесно связаны с другими концепциями параллелизма и синхронизации:
- Process Synchronization — Deadlock возникает из-за неправильной синхронизации доступа к разделяемым ресурсам
- Semaphores & Mutexes — Основные примитивы синхронизации, неправильное использование которых приводит к deadlock
- CPU Scheduling — Планировщик должен учитывать взаимоблокировки и приоритеты при распределении CPU
Вопросы для размышления
- Почему в большинстве современных операционных систем (Linux, Windows) НЕ используется алгоритм банкира, хотя он гарантирует отсутствие deadlock?
- Как спроектировать систему управления ресурсами для многопоточного веб-сервера? Какая стратегия подходит больше - prevention или detection - и почему?
- В распределённой системе (несколько серверов) процессы находятся на разных машинах без общей памяти. Как обнаружить deadlock в таком сценарии?
Связанные уроки
- os-05-sync — Дедлок - патологический исход неправильной синхронизации
- os-03-threads — Дедлок возможен только при конкурентном доступе к ресурсам
- os-17-locks-advanced — Lock ordering и lock-free структуры - практическое решение дедлоков
- db-15-locks