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