std::unique_lock - Создается постоянно в цикле?

А вот такой вопрос - для блокировки потока используется std::unique_lock, но, как я понял он создается именно в функции, которую нужно блокировать. И тут, как бы не совсем понятно, вот к примеру создал поток, сразу усыпил его, потом разбудил, поток выполнил работу, НО функцию не завершается, функция зациклена, и после выполнения одного цикла идет второй, но на втором круге цикла опять встречается строка:

std::unique_lock<std::mutex> my_lock(my_mutex);

И получается, что std::unique_lock - создается постоянно в цикле ? Я имею ввиду не цикл while, а "цикл" goto. Когда код возвращается к метке repeat: и к строке std::unique_lock - получается опять создается новый объект std::unique_lock ?

Вот пример кода:

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
#include <vector>

std::mutex my_mutex;
std::condition_variable my_cond_var;
int global_status;






void waiting_threads(int id)
{
repeat:

    //----------------------------------------------------------------
    {
    std::unique_lock<std::mutex> my_lock(my_mutex);  //Получается он постоянно создается в цикле ?

         while (global_status == 0)
         {
            my_cond_var.wait(my_lock);
         }
    }
    //----------------------------------------------------------------


    std::cout << "thread_id:" << std::this_thread::get_id() << ":" << id << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));   //Какая то работа потока.

goto repeat;

}



void go_1()
{
    global_status = 1;
    my_cond_var.notify_one(); 
}


void go_2()
{
    global_status = 0;
}



void go_3()
{
global_status = 1;
my_cond_var.notify_one(); 
}


int main()
{
    global_status=0;


    int kol_vo_thread = 4;                   //Условное кол-во ядер.


    std::vector<std::thread> my_thread_vec;
    my_thread_vec.resize(kol_vo_thread);


//-----------------------------------------------------
    for (int i = 0; i < kol_vo_thread; i++)
    {
        my_thread_vec[i] = std::thread(waiting_threads, i);
    }
//-----------------------------------------------------




    std::this_thread::sleep_for(std::chrono::seconds(1));            
    go_1();   //Пробуждаем поток.

    std::this_thread::sleep_for(std::chrono::milliseconds(10));  
    go_2();  //Потом усыпляем его.

    std::this_thread::sleep_for(std::chrono::seconds(1)); 
    go_3();  //И опять, когда нужно пробуждаем.


    //------------------------------------------------ 
    for (int i = 0; i < kol_vo_thread; i++)
    {
        my_thread_vec[i].join();
    }
    //------------------------------------------------

}

Ответы (2 шт):

Автор решения: KoVadim

Это классический паттерн. в строке

my_cond_var.wait(my_lock);

не просто происходит ожидание. Внутри функции wait вначале происходит разблокирование мютекса, а при выходе с нее - блокируется назад. К сожалению, иногда эта функция может проснуться "случайно" и для этого как раз и нужен внешний while.

в строке

std::unique_lock<std::mutex> my_lock(my_mutex);

мютекс блокируется, но my_lock запоминает его и при выходе с области видимости автоматически разблоирует

То есть, на самом деле вся эта функция выглядит так

void waiting_threads(int id)
{
repeat:

    //----------------------------------------------------------------
    {
    std::unique_lock<std::mutex> my_lock(my_mutex);  //заблокировали моютекс

         while (global_status == 0)
         {
            my_cond_var.wait(my_lock);
            // разблокировали мютекс
            // ОС не ставит поток в очередь на работу, пока кто то не разбудит
            // если разбудили, лочим мютекс и выходим
         }
     // а вот тут мютекс снова разблокируется.
    }
    //----------------------------------------------------------------


    std::cout << "thread_id:" << std::this_thread::get_id() << ":" << id << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));   //Какая то работа потока.

goto repeat; // Дейкстра был бы в шоке

}

Update

Ручной вариан:


std::condition_variable_any my_cond_var;

void waiting_threads(int id)
{
repeat:

    //----------------------------------------------------------------
    {
         my_mutex.lock();// лочим

         while (global_status == 0)
         {
            my_cond_var.wait(my_mutex);
         }
         my_mutex.unlock();//  разлочим
    }
    //----------------------------------------------------------------


    std::cout << "thread_id:" << std::this_thread::get_id() << ":" << id << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));   //Какая то работа потока.

goto repeat;

}
→ Ссылка
Автор решения: Slava

Судя по всему, вас ввело в заблуждение название unique_lock. Дело в том, что он не "блокирует" функцию, он захватывает мьютекс. Захват мьютекса приводит к тому, что ваша функция засыпает только, если этот же мьютекс захвачен другим потоком, и как только другой поток "отпустит" мьютекс, текущий проснется и получит мьютекс захваченным. Используется для захвата мьютекса объект unique_lock в качестве RAII обертки - как только время жизни этого объекта закончится, он автоматически "отпустит" мьютекс.

Теперь, почему этот объект создается снова и снова. Логика работы обычно такова:

  • захватываем мьютекс
  • проверяем условие, есть ли работа (если нет засыпаем на condition_variable)
  • забираем данные с которыми нужно работать
  • отпускаем мьютекс
  • производим работу
  • повторить

Как видим, для нормальной работы этой функции нужно постоянно захватывать и отпускать мьютекс, поэтому локальный unique_lock создается и уничтожается

→ Ссылка