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 шт):
Это классический паттерн. в строке
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;
}
Судя по всему, вас ввело в заблуждение название unique_lock. Дело в том, что он не "блокирует" функцию, он захватывает мьютекс. Захват мьютекса приводит к тому, что ваша функция засыпает только, если этот же мьютекс захвачен другим потоком, и как только другой поток "отпустит" мьютекс, текущий проснется и получит мьютекс захваченным. Используется для захвата мьютекса объект unique_lock в качестве RAII обертки - как только время жизни этого объекта закончится, он автоматически "отпустит" мьютекс.
Теперь, почему этот объект создается снова и снова. Логика работы обычно такова:
- захватываем мьютекс
- проверяем условие, есть ли работа (если нет засыпаем на condition_variable)
- забираем данные с которыми нужно работать
- отпускаем мьютекс
- производим работу
- повторить
Как видим, для нормальной работы этой функции нужно постоянно захватывать и отпускать мьютекс, поэтому локальный unique_lock создается и уничтожается