Читая о c++ std::lock, я наткнулся на следующий пример из cppreference:
void assign_lunch_partner(Employee &e1, Employee &e2)
{
static std::mutex io_mutex;
{
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl;
}
// use std::lock to acquire two locks without worrying about
// other calls to assign_lunch_partner deadlocking us
{
std::lock(e1.m, e2.m);
std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock);
std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock);
// Equivalent code (if unique_locks are needed, e.g. for condition variables)
// std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
// std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
// std::lock(lk1, lk2);
// Superior solution available in C++17
// std::scoped_lock lk(e1.m, e2.m);
{
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
}
e1.lunch_partners.push_back(e2.id);
e2.lunch_partners.push_back(e1.id);
}
send_mail(e1, e2);
send_mail(e2, e1);
}
Хотя я понимаю необходимость сделать io_mutex как static, чтобы его статус распределялся между одновременными вызовами функции assign_lunch_partner (пожалуйста, поправьте меня, если я ошибаюсь), но я не понимаю следующее:
lk (lock_guard) был ограничен? Это из-за природы lock_guard?lk имеет область действия, не означает ли это, что блокировка будет снята после выхода из области действия?lk (lock_guard)? В начале и непосредственно перед обновлением lunch_partners векторов?@DrewDormann, но я ожидал выполнения параллельных задач в одной и той же области, пока блокировка активна, а не после того, как она вышла за пределы области?
Если только некоторые действия функций должны быть исключительными, вам следует блокировать мьютекс только во время этих действий. Это достигается созданием меньших областей внутри области действия функции.
@Galik, теперь это имеет смысл, спасибо, так что, я думаю, lk(io_mutex) предназначен только для одновременного std::cout?
Прочтите о шаблоне RAII (более общая тема, чем просто блокировка мьютексов). Это должно ответить на все ваши вопросы.
@MarekR, спасибо за рекомендацию





Если вам нужно получить две блокировки, вы можете столкнуться с взаимоблокировкой, если кто-то еще попытается получить те же блокировки в обратном порядке. В этом примере показано, как использовать std::lock, чтобы избежать взаимоблокировки. Сразу после блокировки мьютексы принимаются std::lock_guard объектами, чтобы их можно было разблокировать, как только мы покинем область видимости.
Как уже упоминалось, в C++17 это можно сделать проще с помощью std::scoped_lock.
Вы правильно понимаете, почему io_mutex объявлен как статический; это гарантирует, что все одновременные вызовы функции assign_lunch_partner будут синхронизироваться в одном и том же мьютексе.
Теперь давайте ответим на другие ваши вопросы:
Да, это из-за природы std::lock_guard. std::lock_guard получает блокировку в конструкторе и снимает блокировку в деструкторе. Поместив объект std::lock_guard внутрь области видимости (заключенной фигурными скобками {}), блокировка будет снята при выходе из области видимости, поскольку будет вызван деструктор std::lock_guard.
Да, точно. После выхода из области видимости вызывается деструктор для std::lock_guard и блокировка снимается. Это распространенный шаблон для ограничения продолжительности блокировки только той частью кода, которая требует синхронизации.
Почему объявления lk с ограниченной областью действия (lock_guard) появляются дважды? В начале и непосредственно перед обновлением векторов launch_partners?
Эти две отдельные защиты блокировки с областью действия синхронизируют разные части кода:
lunch_partners. Опять же, это гарантирует, что
вывод консоли из нескольких потоков не чередуется.По сути, эти две отдельные области гарантируют, что сообщения печатаются разумным и упорядоченным образом, даже когда эта функция вызывается из нескольких потоков одновременно. Если бы блокировка io_mutex удерживалась в течение всего времени работы функции, это потенциально могло бы создать узкое место и излишне сериализовать части кода, которые не нужно синхронизировать.
Надеюсь, это проясняет использование защитных блокировок в этом коде!
Да,
std::lock_guardимеет область действия, поэтому она автоматически разблокируется, когда выходит из области действия. Я считаю, что это отвечает на все ваши вопросы.