Std::condition_variable::wait в С++

Я немного запутался в мьютексе, блокировке и ожидании.

Вот мой код:

void producer(std::mutex* m, std::condition_variable* cv,bool* signal) {

    std::this_thread::sleep_for(std::chrono::seconds(1));

    m->lock();
    *signal = true;
    m->unlock();

    std::this_thread::sleep_for(std::chrono::seconds(3));

    cv->notify_one();

    printf("notify one\n");
    std::this_thread::sleep_for(std::chrono::seconds(10000));
}

void consumer(std::mutex*m, std::condition_variable* cv, bool* signal, int index) {

    if (index == 2) std::this_thread::sleep_for(std::chrono::seconds(2));
    std::unique_lock<std::mutex> lk(*m);

    cv->wait(lk, [&] { return (*signal); });

    printf("consumer %d passes this point!\n", index);
    std::this_thread::sleep_for(std::chrono::seconds(10000));
}

int main() {
    
    bool signal = false;
    std::mutex m;
    std::condition_variable cv;
    std::thread th1(producer, &m, &cv, &signal);
    std::thread th2(consumer, &m, &cv, &signal, 1);
    std::thread th3(consumer, &m, &cv, &signal, 2);

    th1.join();
    th2.join();
    th3.join();

    return 0;
}

std::this_thread::sleep_for добавлен, чтобы объяснить мой вопрос. Есть производитель, потребитель1 и потребитель 2. Я думаю, что этот код должен работать следующим образом:

  1. Consumer1 встречает std::unique_lock<std::mutex> lk(*m);, поэтому он блокируется.
  2. потребитель1 встречает cv->wait. Поскольку начальное значение signal равно false, потребитель1 заблокирован, а блокировка снята.
  3. продюсер встречает m->lock();, *signal = true;, m->unlock(); и sleep_for. Следовательно, signal становится true.
  4. потребитель2 встречает std::unique_lock<std::mutex> lk(*m); и cv->wait(lk, [&] { return (*signal); });. Поскольку signal есть true, эта ветка просто пропускает его. Итак, printf("consumer %d passes this point!\n", index); выполняется.
  5. продюсер встречает cv->notify_one();. потребитель 1 разблокирован и проверьте состояние. Поскольку signal — это ture, потребитель1 может пройти эту точку. Следовательно, потребитель1 соответствует printf.

Следовательно, мой ожидаемый результат

consumer 2 passes this point!
notify one
consumer 1 passes this point!

Однако реальный результат

consumer 2 passes this point!
notify one

Получается, что Consumer1 не может передать cv->wait(lk, [&] { return (*signal); });, хотя notify_one() вызывается и условие выполняется. Что-то не так в моем понимании?

вы используете .join(), поэтому ваш код работает синхронно. т.е. производитель завершает, только тогда завершается потребитель 1. Если вы используете .detach(), ваш код будет работать, как и ожидалось (неблокирующий, асинхронный) (кроме выхода из основного потока, поэтому добавьте еще одну переменную условия или переведите его в спящий режим.

t348575 24.12.2020 07:18

Как очень простое практическое правило, никогда не спите, пока вы держите замок.

Jerry Coffin 24.12.2020 08:43
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
132
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы не снимаете блокировку в рутине consumer. Код ниже делает то, что ожидается:

#include <thread>
#include <chrono>
#include <condition_variable>
#include <mutex>

void producer(std::mutex* m, std::condition_variable* cv,bool* signal) {

    std::this_thread::sleep_for(std::chrono::seconds(1));
    {
        std::unique_lock<std::mutex> lk(*m);
        *signal = true;
    }
    std::this_thread::sleep_for(std::chrono::seconds(3));

    cv->notify_all();

    printf("notify one\n");
    std::this_thread::sleep_for(std::chrono::seconds(5));
}

void consumer(std::mutex*m, std::condition_variable* cv, bool* signal, int index) {

    if (index == 2) std::this_thread::sleep_for(std::chrono::seconds(2));

    {
        std::unique_lock<std::mutex> lk(*m);
        cv->wait(lk, [&] { return (*signal); });
    }

    printf("consumer %d passes this point!\n", index);
    std::this_thread::sleep_for(std::chrono::seconds(5));
}

int main() {

    bool signal = false;
    std::mutex m;
    std::condition_variable cv;
    std::thread th1(producer, &m, &cv, &signal);
    std::thread th2(consumer, &m, &cv, &signal, 1);
    std::thread th3(consumer, &m, &cv, &signal, 2);

    th1.join();
    th2.join();
    th3.join();

    return 0;
}

Обратите внимание на скобки вокруг std::unique_lock<std::mutex> lk(*m);, чтобы обеспечить локальную область действия.

Ответ принят как подходящий

Проблема в том, что потребитель 2 не освобождает блокировку мьютекса перед тем, как заснуть:

std::unique_lock<std::mutex> lk(*m);

cv->wait(lk, [&] { return (*signal); });

printf("consumer %d passes this point!\n", index);   // <-- mutex is still locked here
std::this_thread::sleep_for(std::chrono::seconds(10000));

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

std::unique_lock дает вам более детальный контроль, чем его аналог std::lock_guard, включая возможность разблокировки. Что вы можете сделать, так это добавить вызов разблокировки перед тем, как потребитель заснет, например:

...
lk.unlock();
std::this_thread::sleep_for(std::chrono::seconds(10000));

В противном случае блокировка мьютекса снимается только после того, как потребитель 2 завершит выполнение функции consumer(), то есть после длительного сна.

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