← Назад к вопросам

Что такое DeadLock?

1.3 Junior🔥 251 комментариев
#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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 условия:

  1. Mutual Exclusion — ресурс не может использоваться двумя потоками одновременно
  2. Hold and Wait — поток удерживает ресурс и ждёт другого
  3. No Preemption — нельзя отнять ресурс у потока насильно
  4. 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 вместо многопоточности