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

Сколько нужно Mutex, чтобы создать Deadlock?

1.3 Junior🔥 121 комментариев
#Многопоточность и синхронизация

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

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

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

Минимальное количество мьютексов для дедлока

Теоретический ответ: Для создания дедлока необходимо минимум 2 мьютекса и 2 потока. Это фундаментальное условие, которое встречается во всех операционных системах и многопоточных приложениях.

Почему именно 2 мьютекса?

Дедлок (deadlock) — это состояние, при котором два или более процесса взаимно ждут друг друга. Невозможно создать дедлок с одним мьютексом, так как никакого циклического ожидания не возникает.

Условия дедлока (Coffman conditions):

  1. Взаимное исключение — ресурс может использовать только один процесс
  2. Удержание и ожидание — процесс держит ресурс и ждёт другого
  3. Отсутствие вытеснения — ресурс нельзя отобрать, только отпустить
  4. Циклическое ожидание — А ждёт Б, Б ждёт В, ..., Z ждёт А

Классический пример с двумя мьютексами

#include <thread>
#include <mutex>
#include <chrono>

std::mutex mutex1, mutex2;

void thread1_func() {
    std::lock_guard<std::mutex> lock1(mutex1);
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    std::lock_guard<std::mutex> lock2(mutex2);  // DEADLOCK!
    std::cout << "Thread 1 completed" << std::endl;
}

void thread2_func() {
    std::lock_guard<std::mutex> lock2(mutex2);
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    std::lock_guard<std::mutex> lock1(mutex1);  // DEADLOCK!
    std::cout << "Thread 2 completed" << std::endl;
}

int main() {
    std::thread t1(thread1_func);
    std::thread t2(thread2_func);
    t1.join();
    t2.join();
    return 0;
}

Как избежать дедлока

1. Упорядочение ресурсов — всегда захватывать в одном порядке:

void thread1_func() {
    std::lock_guard<std::mutex> lock1(mutex1);
    std::lock_guard<std::mutex> lock2(mutex2);
    // Работа с обоими ресурсами
}

void thread2_func() {
    std::lock_guard<std::mutex> lock1(mutex1);  // ТОТ ЖЕ порядок!
    std::lock_guard<std::mutex> lock2(mutex2);
    // Работа с обоими ресурсами
}

2. Использование std::lock для атомного захвата:

void thread1_func() {
    std::lock(mutex1, mutex2);  // Атомно захватываем оба
    std::lock_guard<std::mutex> lock1(mutex1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mutex2, std::adopt_lock);
}

3. Установка тайм-аутов:

std::timed_mutex timed_mutex1, timed_mutex2;

void thread1_func() {
    if (timed_mutex1.try_lock_for(std::chrono::milliseconds(100))) {
        if (timed_mutex2.try_lock_for(std::chrono::milliseconds(100))) {
            // Работа...
            timed_mutex2.unlock();
        } else {
            timed_mutex1.unlock();
        }
    }
}

Практические рекомендации

  • Минимизируй время захвата — освобождай мьютекс как можно скорее
  • Избегай вложенного захвата — используй std::lock если нужны несколько мьютексов
  • Используй higher-level примитивы — condition variables, readers-writers locks
  • Проектируй с нуля — планируй синхронизацию заранее
  • Тестируй многопоточность — дедлоки проявляются непредсказуемо

Итого: для дедлока нужно минимум 2 мьютекса и правильное (неправильное!) расписание потоков.

Сколько нужно Mutex, чтобы создать Deadlock? | PrepBro