Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
DeadLock (Мёртвая блокировка) в многопоточности
Deadlock — это состояние, когда два или более потока бесконечно ждут друг друга, что приводит к полной остановке приложения. Это одна из самых сложных проблем в многопоточном программировании.
Классический пример
Представь, что у тебя есть два человека и две палочки для еды (chopsticks). Каждый человек должен взять обе палочки, чтобы поесть.
Поток 1: берет палочку A → ждет палочку B
Поток 2: берет палочку B → ждет палочку A
Каждый поток занял один ресурс и ждет другой. Они ждут друг друга бесконечно — это DeadLock!
Четыре условия DeadLock (все должны быть выполнены)
1. Mutual Exclusion (Взаимное исключение) Ресурс не может быть использован двумя потоками одновременно:
final mutex = Mutex();
await mutex.lock(); // Только один поток может владеть блокировкой
2. Hold and Wait (Держание и ожидание) Поток держит ресурс и ждет другого:
await lock1.lock();
// ... вот тут может наступить deadlock
await lock2.lock();
3. No Preemption (Невозможность отобрания) Ресурс нельзя отобрать, пока поток его держит:
// Нельзя просто так взять ресурс у другого потока
// Нужно дождаться, пока он его освободит
4. Circular Wait (Циклическое ожидание) Цепь потоков, где каждый ждет ресурс от следующего:
Поток A → ждет Поток B
Поток B → ждет Поток C
Поток C → ждет Поток A (цикл!)
Практический пример DeadLock в Flutter/Dart
class BankAccount {
int balance = 100;
final Completer<void> _lockCompleter = Completer();
Future<void> transfer(int amount, BankAccount other) async {
// Поток 1: блокирует это счете
await _lockCompleter.future; // Имитация lock
// Пытается заблокировать другой счет
await other._lockCompleter.future; // DEADLOCK!
balance -= amount;
other.balance += amount;
}
}
// Сценарий deadlock:
// Поток 1: account1.transfer(10, account2) → берет account1, ждет account2
// Поток 2: account2.transfer(20, account1) → берет account2, ждет account1
// Результат: оба потока ждут друг друга бесконечно
Реальный пример в многопоточности
class Resource {
final String name;
Resource(this.name);
}
final resourceA = Resource('A');
final resourceB = Resource('B');
// ❌ DEADLOCK SCENARIO
Future<void> deadlockExample() async {
Future<void> thread1() async {
synchronized(resourceA) { // Берет ResourceA
await Future.delayed(Duration(milliseconds: 100));
synchronized(resourceB) { // Ждет ResourceB
print('Thread 1 has both resources');
}
}
}
Future<void> thread2() async {
synchronized(resourceB) { // Берет ResourceB
await Future.delayed(Duration(milliseconds: 100));
synchronized(resourceA) { // Ждет ResourceA
print('Thread 2 has both resources');
}
}
}
// Запускаем оба потока одновременно
await Future.wait([thread1(), thread2()]);
// ⚠️ Программа зависнет!
}
Как избежать DeadLock
1. Всегда блокируй ресурсы в одном порядке
// ✅ ПРАВИЛЬНО: всегда сначала A, потом B
Future<void> safeTransfer() async {
// Поток 1
synchronized(resourceA) {
synchronized(resourceB) {
// Операция
}
}
}
Future<void> safeTransfer2() async {
// Поток 2 тоже в том же порядке
synchronized(resourceA) {
synchronized(resourceB) {
// Операция
}
}
}
2. Установи timeout
Future<void> transferWithTimeout() async {
try {
await lock1.lock();
final result = await lock2.lock().timeout(
Duration(seconds: 5),
onTimeout: () => throw TimeoutException('DeadLock detected'),
);
// Операция
} catch (e) {
print('Lock timeout or error: $e');
} finally {
lock1.unlock();
lock2.unlock();
}
}
3. Используй atomic операции и CAS (Compare-And-Swap)
// Вместо множественных блокировок, используй одну атомарную операцию
class AtomicAccount {
int _balance = 0;
final Mutex _lock = Mutex();
Future<bool> transferAtomic(int amount) async {
return await _lock.protect(() async {
if (_balance >= amount) {
_balance -= amount;
return true;
}
return false;
});
}
}
4. Используй higher-level abstractions
// Вместо низкоуровневых lock/unlock, используй:
import 'package:synchronized/synchronized.dart';
final lock = Lock();
future<void> safeOperation() async {
await lock.synchronized(() async {
// Код выполняется с гарантией синхронизации
// Нет риска deadlock благодаря высокоуровневому API
});
}
Детектирование DeadLock
class DeadlockDetector {
static Future<T> executeWithTimeout<T>(
Future<T> Function() operation,
Duration timeout,
) async {
try {
return await operation().timeout(timeout);
} on TimeoutException {
print('⚠️ Possible deadlock detected!');
rethrow;
}
}
}
// Использование
await DeadlockDetector.executeWithTimeout(
() => complexOperation(),
Duration(seconds: 10),
);
DeadLock в контексте Flutter
UI Thread DeadLock
// ❌ ПЛОХО: блокируешь UI thread
void onButtonPressed() {
// Длинная синхронная операция → UI зависает
Thread.sleep(5000);
// Даже если другой код ждет UI, ничего не происходит
}
// ✅ ХОРОШО: используй async/await
void onButtonPressed() async {
await longOperation(); // UI остается отзывчивым
}
Практические правила
- Минимизируй время, когда поток держит lock
- Всегда освобождай lock (используй try-finally или synchronized)
- Избегай вложенных locks где возможно
- Документируй порядок, в котором должны приниматься locks
- Используй tools для детектирования (unit tests, static analysis)
- Тестируй многопоточные сценарии (race conditions, stress tests)
Заключение
Deadlock — это призрак многопоточности. За 10+ лет опыта я вижу, что это одна из самых сложных багов для отладки, потому что она непредсказуема и сложно воспроизводится. Ключ к избежанию:
- Простота — используй высокоуровневые abstractions
- Осторожность — знай все четыре условия deadlock
- Тестирование — stress-тесты и сценарии race conditions