Ответ
Deadlock (взаимная блокировка) — это ситуация в многопоточном программировании, когда два или более потока заблокированы, каждый ожидает освобождения ресурса, удерживаемого другим, создавая циклическую зависимость.
Классический пример на C++ с std::mutex:
#include <thread>
#include <mutex>
#include <chrono>
std::mutex mutex1, mutex2;
void thread1_work() {
std::lock_guard<std::mutex> lock1(mutex1); // Захватываем mutex1
std::this_thread::sleep_for(std::chrono::milliseconds(1)); // Имитация работы
std::lock_guard<std::mutex> lock2(mutex2); // Блокировка: ждём mutex2, который у thread2
// Критическая секция 1&2
}
void thread2_work() {
std::lock_guard<std::mutex> lock2(mutex2); // Захватываем mutex2
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::lock_guard<std::mutex> lock1(mutex1); // Блокировка: ждём mutex1, который у thread1
// Критическая секция 2&1
}
// Запуск t1 и t2 приведёт к deadlock.
Способы предотвращения, которые я применяю:
- Упорядоченная блокировка: Всегда захватывать мьютексы в одном и том же глобальном порядке (напр., сначала
mutex1, потомmutex2). - Использование
std::lock: Стандартная функция, которая безопасно захватывает несколько мьютексов, избегая deadlock.std::lock(mutex1, mutex2); // Атомарно захватывает оба std::lock_guard<std::mutex> lk1(mutex1, std::adopt_lock); std::lock_guard<std::mutex> lk2(mutex2, std::adopt_lock); - Избегание вложенных блокировок: Перепроектирование кода, чтобы уменьшить необходимость удерживать несколько мьютексов.
- Использование
std::scoped_lock(C++17): Более современная и безопасная заменаstd::lock_guardдля нескольких мьютексов.std::scoped_lock lock(mutex1, mutex2); // Deadlock-free захват
Ответ 18+ 🔞
Да ты посмотри, какая хитрая жопа эта deadlock-ситуация! Представь себе, два потока уперлись, как бараны, и оба ждут, пока другой отпустит свой ресурс. Получается этакая циклическая зависимость, ёпта, из которой нихуя не вырваться — они так и будут висеть до скончания времён, если ты, конечно, не прибьёшь процесс.
Вот тебе классический пример на C++, прям как в жизни:
#include <thread>
#include <mutex>
#include <chrono>
std::mutex mutex1, mutex2;
void thread1_work() {
std::lock_guard<std::mutex> lock1(mutex1); // Хватаем первый мьютекс
std::this_thread::sleep_for(std::chrono::milliseconds(1)); // Немного потягиваем кофе
std::lock_guard<std::mutex> lock2(mutex2); // БЛОКИРОВКА: ждём второй, а он у второго потока!
// Критическая секция 1&2
}
void thread2_work() {
std::lock_guard<std::mutex> lock2(mutex2); // Хватаем второй мьютекс
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::lock_guard<std::mutex> lock1(mutex1); // БЛОКИРОВКА: ждём первый, а он у первого!
// Критическая секция 2&1
}
// Запустишь t1 и t2 — и всё, накрылся медным тазом, взаимная блокировка.
Чтобы не было тебе хиросимы и нагасаки в коде, вот как я с этим борюсь:
- Порядок — всё! Это как в армии: всегда хватай мьютексы в одном и том же порядке. Сначала
mutex1, потомmutex2, и никаких вольностей. Тогда цикла не получится, даже если очень захотеть. - Волшебный
std::lock. Эта штука атомарно хватает несколько мьютексов сразу, не давая deadlock'у ни единого шанса. Красота, а не функция.std::lock(mutex1, mutex2); // Берёт оба сразу, без души std::lock_guard<std::mutex> lk1(mutex1, std::adopt_lock); std::lock_guard<std::mutex> lk2(mutex2, std::adopt_lock); - Не плоди сущности. Самый мудрый способ — перепроектировать логику, чтобы не нужно было держать кучу блокировок одновременно. Иногда это овердохуища работы, но зато надёжно.
- Новая метла
std::scoped_lock(C++17). Современная замена, которая делает всё красиво и безопасно в одну строчку. Просто пишешь и спишь спокойно.std::scoped_lock lock(mutex1, mutex2); // Deadlock-free, как швейцарские часы
Главное, чувак, — не бздеть, а думать головой. А то подловит тебя этот deadlock, и будешь потом сидеть с отладчиком, материться и чувствовать себя полным ебанько.
Видео-ответы
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶