В какой ситуации может возникнуть Deadlock?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое Deadlock (Взаимная блокировка)?
Deadlock — это ситуация в многозадачной среде, когда два или более процесса бесконечно ожидают друг друга, поскольку каждый удерживает ресурс, необходимый другому процессу, и при этом сам ожидает освобождения ресурса, удерживаемого другим процессом. Система "зависает" без возможности прогресса.
Условия возникновения Deadlock
Для возникновения взаимной блокировки необходимы четыре обязательных условия (условия Коффмана):
- Взаимное исключение (Mutual Exclusion) — ресурс не может быть использован более чем одним процессом одновременно.
- Удержание и ожидание (Hold and Wait) — процесс, удерживая хотя бы один ресурс, запрашивает дополнительный ресурс, который в данный момент занят другим процессом.
- Отсутствие вытеснения (No Preemption) — ресурс может быть освобождён только добровольно процессом, который его удерживает.
- Кольцевое ожидание (Circular Wait) — существует циклическая цепочка процессов, где каждый процесс ожидает ресурс, удерживаемый следующим процессом в цепочке.
Типичные ситуации Deadlock в PHP Backend
1. Блокировки в базах данных
Наиболее частый сценарий в веб-приложениях — транзакционные блокировки.
-- Сессия 1
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
-- Удерживает блокировку строки id=1
-- Сессия 2
START TRANSACTION;
UPDATE users SET balance = balance + 50 WHERE id = 2;
UPDATE users SET balance = balance - 50 WHERE id = 1;
-- Ожидает блокировку строки id=1, которую удерживает Сессия 1
-- Сессия 1 (продолжение)
UPDATE users SET balance = balance + 100 WHERE id = 2;
-- Ожидает блокировку строки id=2, которую удерживает Сессия 2
-- DEADLOCK!
Результат: База данных обнаружит deadlock и откатит одну из транзакций.
2. Конкурентный доступ к файлам
PHP-скрипты могут конфликтовать при работе с одними файлами.
// Скрипт 1
$fp1 = fopen('fileA.txt', 'r+');
flock($fp1, LOCK_EX); // Блокирует fileA
sleep(2);
$fp2 = fopen('fileB.txt', 'r+');
flock($fp2, LOCK_EX); // Ожидает fileB
// Скрипт 2 (запущен параллельно)
$fp2 = fopen('fileB.txt', 'r+');
flock($fp2, LOCK_EX); // Блокирует fileB
sleep(2);
$fp1 = fopen('fileA.txt', 'r+');
flock($fp1, LOCK_EX); // Ожидает fileA
// DEADLOCK!
3. Взаимные вызовы в распределённых системах
В микросервисной архитектуре deadlock может возникнуть на уровне HTTP/RPC-вызовов.
// Сервис A вызывает сервис B
class ServiceA {
public function processOrder() {
$lock = $this->acquireLock('order:123');
$response = $this->httpClient->post('service-b/reserve');
// Ожидает ответ от Service B
}
}
// Сервис B вызывает сервис A
class ServiceB {
public function reserveItem() {
$lock = $this->acquireLock('item:456');
$response = $this->httpClient->post('service-a/validate');
// Ожидает ответ от Service A
}
}
4. Взаимные блокировки в кеше (Redis/Memcached)
При использовании SETNX или Redlock для распределённых блокировок.
// Процесс 1
$redis->setnx('lock:resource1', 1); // Блокирует resource1
sleep(1);
$locked = $redis->setnx('lock:resource2', 1); // Ожидает resource2
// Процесс 2
$redis->setnx('lock:resource2', 1); // Блокирует resource2
sleep(1);
$locked = $redis->setnx('lock:resource1', 1); // Ожидает resource1
Стратегии предотвращения Deadlock
Проактивные методы:
- Упорядочивание блокировок — всегда захватывать ресурсы в строго определённом порядке.
- Таймауты — использовать
flock($fp, LOCK_EX | LOCK_NB)илиSETNXс таймаутом. - Отказ от взаимного исключения — использовать атомарные операции, CAS (Compare-and-Swap).
Реактивные методы:
- Обнаружение deadlock — системы вроде InnoDB имеют встроенные детекторы.
- Разрушение deadlock — принудительный откат одной из транзакций (DB делает это автоматически).
Практические рекомендации для PHP-разработчиков
- Всегда соблюдайте порядок блокировок в коде:
// Правильно: всегда сначала resource1, потом resource2
$lock1 = $this->lockManager->acquire('resource1');
$lock2 = $this->lockManager->acquire('resource2');
// Опасно: разный порядок в разных местах кода
- Используйте таймауты для всех операций блокировки.
- Минимизируйте время удержания блокировок — выполняйте минимальную работу внутри критической секции.
- Применяйте идемпотентные операции там, где это возможно.
- Используйте
SELECT ... FOR UPDATE NOWAITв PostgreSQL илиNOWAIT/SKIP LOCKEDв MySQL 8+.
Deadlock — не теоретическая проблема, а реальная угроза для высоконагруженных PHP-приложений. Грамотное проектирование транзакций, корректное использование блокировок и понимание механизмов СУБД позволяют снизить вероятность взаимных блокировок до приемлемого уровня.