Deadlock и состояние гонки
Что такое deadlock?
Deadlock (взаимная блокировка) — это состояние, при котором два или
более потоков навсегда заблокированы, ожидая ресурсы, захваченные друг другом.
Это приводит к остановке выполнения программы.
Пример:
-
Поток A захватывает ресурс 1 и ждёт ресурс 2.
-
Поток B захватывает ресурс 2 и ждёт ресурс 1.
-
Оба потока остаются заблокированными, ожидая освобождения ресурсов.
Причины:
-
Неправильный порядок захвата ресурсов.
-
Нарушение правил синхронизации.
Предотвращение:
-
Использование таймаутов при захвате мьютексов.
-
Соблюдение единого порядка захвата ресурсов.
-
Применение высокоуровневых средств синхронизации (например,
std::lockдля атомарного захвата нескольких мьютексов).
Пример:
std::mutex mtx1, mtx2;
void threadA() {
std::lock_guard<std::mutex> lock1(mtx1);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock2(mtx2);
}
void threadB() {
std::lock_guard<std::mutex> lock2(mtx2);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock1(mtx1);
}
std::thread t1(threadA);
std::thread t2(threadB);
t1.join();
t2.join();
Как избежать состояния гонки?
Чтобы избежать состояния гонки (race condition) в многопоточном программировании, необходимо правильно синхронизировать доступ к общим ресурсам. Вот несколько методов:
-
Использование мьютексов (
std::mutex):- Защищает критические секции кода, обеспечивая доступ к ним только одному потоку за раз.
std::mutex mtx; void threadFunc() { std::lock_guard<std::mutex> lock(mtx); // Доступ к общим данным } -
Использование атомарных операций (
std::atomic):- Обеспечивает атомарные обращения к отдельному объекту. Несколько операций над ним или согласование нескольких переменных требуют отдельного анализа. Реализация не всегда lock-free.
std::atomic<int> counter(0); void increment() { counter++; } -
Использование условных переменных (
std::condition_variable):- Синхронизирует потоки, ожидая выполнения определённых условий.
std::mutex mtx; std::condition_variable cv; bool ready = false; void producer() { std::lock_guard<std::mutex> lock(mtx); ready = true; cv.notify_one(); } void consumer() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return ready; }); // Доступ к общим данным } -
Соблюдение порядка захвата ресурсов:
- Захват ресурсов всегда в одном и том же порядке для всех потоков.
std::mutex mtx1, mtx2; void threadFunc() { std::lock(mtx1, mtx2); std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock); std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock); // Доступ к общим данным }