Что такое взаимная блокировка и как её избежать?
- Что такое deadlock?
- Какие условия нужны для дедлока?
- Чем livelock отличается от deadlock?
- Как найти дедлок в работающем приложении?
Взаимная блокировка — потоки ждут друг друга по кругу: первый держит A и ждёт B, второй держит B и ждёт A. Никто не продвинется.
- Нужны все 4 условия Коффмана: взаимное исключение, удержание с ожиданием, нет отнятия, цикл ожидания
- Главная профилактика — единый порядок захвата блокировок
- Ещё: tryLock с таймаутом, одна крупная блокировка, отказ от вложенных блокировок
А ещё Livelock — потоки активно уступают друг другу и тоже не продвигаются; голодание — поток бесконечно ждёт своей очереди
Подробно
Условия Коффмана — дедлок возможен, только если выполнены все четыре:
- взаимное исключение — ресурс может держать только один;
- удержание и ожидание — поток держит один ресурс и ждёт другой;
- нет принудительного отнятия — ресурс нельзя забрать, только отпустить добровольно;
- циклическое ожидание — есть цикл в графе «кто кого ждёт».
Профилактика — сломать хотя бы одно:
- единый порядок блокировок (ломает цикл) — например, всегда сначала счёт с меньшим id. Классический перевод денег
transfer(from, to)блокирует оба счёта; если один поток переводит A→B, а другой B→A, при захвате «сначала from» получится дедлок. Захват по порядку id устраняет его; - tryLock с таймаутом (ломает «нет отнятия» — поток сам отказывается): не получилось за время — отпустить всё, подождать случайное время и повторить;
- захват всех ресурсов сразу или одна общая блокировка (ломает удержание с ожиданием) — проще, но меньше параллелизма;
- не вызывать чужой код под блокировкой — колбэки и слушатели могут захватить что-то своё.
В базах данных дедлоки — обычное дело: две транзакции обновляют строки в разном порядке. СУБД строит граф ожиданий, находит цикл и откатывает одну транзакцию с ошибкой (deadlock detected в PostgreSQL). Приложение должно уметь повторить транзакцию; профилактика та же — обновлять строки в одном порядке.
Livelock — потоки не заблокированы, но не продвигаются: оба вежливо отпускают ресурс, видя конфликт, и снова сталкиваются. Лечится случайной задержкой перед повтором. Голодание — поток в принципе может продвинуться, но ему постоянно не достаётся ресурс (несправедливая блокировка, низкий приоритет, писатель при потоке читателей).
Диагностика: в Java jstack <pid> или дамп потоков сам пишет «Found one Java-level deadlock» с цепочкой; ThreadMXBean.findDeadlockedThreads. Симптом — процесс жив, CPU около нуля, запросы висят.
Пример
void transfer(Account from, Account to, long amount) {
Account first = from.id < to.id ? from : to; // единый порядок
Account second = first == from ? to : from;
synchronized (first) {
synchronized (second) {
from.withdraw(amount);
to.deposit(amount);
}
}
}Что хотят услышать
- Определение через циклическое ожидание и пример с двумя блокировками.
- Четыре условия Коффмана.
- Способы предотвращения: порядок блокировок, tryLock с таймаутом.
- Дедлоки в базе данных и повтор транзакции.
- Отличие от livelock и голодания, диагностика — плюс.
Частые ошибки
- Путать дедлок с гонкой.
- Предлагать «увеличить таймауты» как решение.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✓Потоки ждут друг друга по кругу; главная профилактика — единый порядок захвата блокировок
- ✗Взаимная блокировка невозможна при двух потоках
- ✗Потоки ждут друг друга по кругу; лечится увеличением числа потоков в пуле, чтобы всегда оставался свободный поток для снятия блокировки
- ✗Взаимная блокировка возникает, только когда ресурсов больше, чем потоков
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.