Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
DeadLock (Мёртвый замок)
DeadLock — это одна из самых коварных ошибок в многопоточном программировании. За 10 лет я встречал разработчиков, которые годами избегали многопоточности только из-за страха перед deadlock'ами. На самом деле это просто, если понимать механизм.
Определение
DeadLock возникает, когда два или более потока永久 блокируют друг друга, ожидая ресурсов, которые удерживают друг друга. Результат: оба потока зависают, приложение замерзает на этом месте.
Классический пример с двумя потоками
private static readonly object Lock1 = new object();
private static readonly object Lock2 = new object();
// Поток 1
lock (Lock1)
{
System.Threading.Thread.Sleep(100);
lock (Lock2) // Ждёт Lock2
{
Console.WriteLine("Поток 1 работает");
}
}
// Поток 2 (одновременно)
lock (Lock2)
{
System.Threading.Thread.Sleep(100);
lock (Lock1) // Ждёт Lock1 — DEADLOCK!
{
Console.WriteLine("Поток 2 работает");
}
}
Поток 1 удерживает Lock1 и ждёт Lock2. Поток 2 удерживает Lock2 и ждёт Lock1. ✗ Взаимная блокировка. Приложение зависает навечно.
Четыре условия DeadLock'а
Для возникновения deadlock'а одновременно должны быть все 4 условия:
- Mutual Exclusion — ресурс не может использоваться двумя потоками одновременно
- Hold and Wait — поток удерживает ресурс и ждёт другого
- No Preemption — нельзя отнять ресурс у потока насильно
- Circular Wait — образуется цикл: поток A ждёт ресурса B, поток B ждёт ресурса A
Чтобы избежать deadlock'а, нарушь одно из этих условий.
Как избежать DeadLock
1. Наиболее надёжный способ — избежь вложенных lock'ов
lock (Lock1)
{
// Делаем работу с Lock1
DoSomething();
// Выходим из lock
}
lock (Lock2)
{
// Отдельно работаем с Lock2
DoSomethingElse();
}
2. Всегда получай ресурсы в одном порядке
// Поток 1 И Поток 2 получают Lock1 ПЕРЕД Lock2
lock (Lock1)
{
lock (Lock2)
{
// Безопасно
}
}
3. Используй Timeout
if (Monitor.TryEnter(Lock1, TimeSpan.FromSeconds(1)))
{
try
{
// Работаем
}
finally
{
Monitor.Exit(Lock1);
}
}
else
{
Console.WriteLine("Не смог получить lock");
}
4. Используй современные подходы: ReaderWriterLockSlim, SemaphoreSlim
private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
await _semaphore.WaitAsync();
try
{
// Критическая секция
}
finally
{
_semaphore.Release();
}
5. Лучший способ — асинхронность вместо многопоточности
// Избегай Thread.Sleep и блокирующих операций
// Используй async/await
await Task.Delay(100);
Deadlock в базах данных
Deadlock'и встречаются и в SQL (особенно в PostgreSQL и MSSQL):
-- Транзакция 1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
WAIT -- ждёт UPDATE WHERE id = 2
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- Транзакция 2
BEGIN;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
WAIT -- ждёт UPDATE WHERE id = 1
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
Решение: всегда обновляй строки в одинаковом порядке по ID.
Выводы для интервью
- DeadLock — серьёзная проблема в параллелизме
- Избегай вложенных lock'ов
- Получай ресурсы в одном порядке
- Используй timeout'ы для безопасности
- В C# предпочитай async/await вместо многопоточности