cout << "bad f1 got both mutexes." << endl;
}
4. Как мы и говорили на предыдущем шаге, функция deadlock_func_2
deadlock_func_1
, но блокирует мьютексы A
и B
в противоположном порядке:static void deadlock_func_2()
{
cout << "bad f2 acquiring mutex B..." << endl;
lock_guard
this_thread::sleep_for(100ms);
cout << "bad f2 acquiring mutex A..." << endl;
lock_guard
cout << "bad f2 got both mutexes." << endl;
}
5. Теперь напишем свободный от взаимных блокировок вариант этих функций. Они используют класс scoped_lock
А
и В
в разном порядке:static void sane_func_1()
{
scoped_lock l {mut_a, mut_b};
cout << "sane f1 got both mutexes." << endl;
}
static void sane_func_2()
{
scoped_lock l {mut_b, mut_a};
cout << "sane f2 got both mutexes." << endl;
}
6. В функции main
int main()
{
{
thread t1 {sane_func_1};
thread t2 {sane_func_2};
t1.join();
t2.join();
}
7. Затем воспользуемся функциями, создающими взаимные блокировки, которые не следуют стратегиям избегания взаимных блокировок:
{
thread t1 {deadlock_func_1};
thread t2 {deadlock_func_2};
t1.join();
t2.join();
}
}
8. Компиляция и запуск программы дадут следующий результат. В первых двух строках показывается, что
А
и В
, а затем вечно ожидают. Обе функции не достигают момента, когда успешно блокируют оба мьютекса. Можно оставить программу включенной на часы, дни и годы, и ничего не произойдет.Это приложение нужно завершить снаружи, например нажав Ctrl+C:
$ ./avoid_deadlock
sane f1 got both mutexes
sane f2 got both mutexes
bad f2 acquiring mutex B...
bad f1 acquiring mutex A...
bad f1 acquiring mutex B...
bad f2 acquiring mutex A...
Как это работает
Реализуя код, намеренно вызывающий взаимную блокировку, мы увидели, как быстро может возникнуть этот нежелательный сценарий. В крупном проекте, где несколько программистов пишут код, который должен разделять один набор ресурсов, защищенных мьютексами, всем программистам необходимо следовать
В подобных ситуациях поможет scoped_lock
lock_guard
и unique_lock
: его конструктор выполняет блокирование, а его деструктор разблокирует мьютекс. Класс может работать с Класс scoped_lock
std::lock
, которая применяет особый алгоритм, выполняющий набор вызовов try_lock
для всех предоставленных мьютексов, что позволяет предотвратить взаимные блокировки. Поэтому совершенно безопасно задействовать scoped_lock
или вызывать std::lock
для одного набора блокировок, но в разном порядке. Синхронизация конкурентного использования std::cout
Многопоточные программы неудобны тем, что нужно охранять