Я немного запутался в мьютексе, блокировке и ожидании.
Вот мой код:
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. Я думаю, что этот код должен работать следующим образом:
std::unique_lock<std::mutex> lk(*m);, поэтому он блокируется.cv->wait. Поскольку начальное значение signal равно false, потребитель1 заблокирован, а блокировка снята.m->lock();, *signal = true;, m->unlock(); и sleep_for. Следовательно, signal становится true.std::unique_lock<std::mutex> lk(*m); и cv->wait(lk, [&] { return (*signal); });. Поскольку signal есть true, эта ветка просто пропускает его. Итак, printf("consumer %d passes this point!\n", index); выполняется.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() вызывается и условие выполняется. Что-то не так в моем понимании?
Как очень простое практическое правило, никогда не спите, пока вы держите замок.





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