Понимание примера 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(поправьте меня, если я ошибаюсь), но я не понимаю следующего:

  1. Почему объект() был ограничен? Это из-за природы?
  2. Тогда, если область действия ограничена, не означает ли это, что блокировка будет снята, как только она выйдет за пределы области действия?
  3. Почему существует двойное объявление области видимости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) с ограниченной областью действия? В начале и непосредственно перед обновлением векторов обеда_партнеров?

Эти два отдельных ограничителя блокировки с областью действия синхронизируют разные части кода:

  1. Первый используется для синхронизации вывода на консоль, который сообщает вам, что два сотрудника ждут блокировки. Это гарантирует, что если несколько потоков одновременно выполняют эту функцию, их выходные данные не перепутаются.
  2. Второй используется для синхронизации вывода на консоль, который сообщает вам, что два сотрудника получили блокировки и готовы обновить данные.lunch_partnersвекторы. Опять же, это гарантирует, что вывод консоли из нескольких потоков не будет чередоваться.

По сути, эти две отдельные области гарантируют, что сообщения печатаются разумным и упорядоченным образом, даже если эта функция вызывается из нескольких потоков одновременно. Еслиio_mutexlock удерживались в течение всего времени выполнения функции, это потенциально может создать узкое место и излишне сериализовать части кода, которые не нуждаются в синхронизации.

Надеюсь, это прояснило использование средств блокировки в этом коде!

Другие вопросы по тегам