C ++ 11 — не удается разбудить поток, используя std :: thread и std :: condition_variable

Я застрял в проблеме при попытке разбудить поток другим. Простая вещь производитель / потребитель.

Ниже код. В строке 85 я не понимаю, почему она не работает. Поток производителя заполняет std :: queue и вызывает std :: condition_variable.notify_one (), в то время как поток потребителя ожидает NOT std :: queue.empty ().

Заранее благодарю за любую помощь

#include <mutex>
#include <condition_variable>
#include <queue>
#include <string>
#include <iostream>
#include <thread>

// request
class request :
public std::mutex,
public std::condition_variable,
public std::queue<std::string>
{
public:
virtual ~request();
};

request::~request()
{
}

// producer
class producer
{
public:
producer(request &);

virtual ~producer();

void operator()();

private:
request & request_;
};

producer::producer(request & _request)
:
request_(_request)
{
}

producer::~producer()
{
}

void
producer::operator()()
{
while (true) {
std::lock_guard<std::mutex> lock(request_);
std::cout << "producer\n";
request_.push("something");
std::this_thread::sleep_for(std::chrono::seconds(1));
request_.notify_one();
}
}

class consumer
{
public:
consumer(request &);

virtual ~consumer();

void operator()();

private:
request & request_;
};

consumer::consumer(request & _request)
:
request_(_request)
{
}

consumer::~consumer()
{
}

void
consumer::operator()()
{
while (true) {
std::unique_lock<std::mutex> lock(request_); // <-- the problem
std::cout << "consumer\n";
request_.wait (
lock, [this] {return !request_.empty();}
);
request_.pop();
}
}

int
main()
{
// request
request request_;

// producer
std::thread producer_{producer(request_)};

// consumer
std::thread first_consumer_{consumer(request_)};
std::thread second_consumer_{consumer(request_)};

// join
producer_.join();
first_consumer_.join();
second_consumer_.join();
}

5

Решение

Исправленный код ниже, с этими изменениями:

  • Не происходи от мьютекса, condvar и очереди как это, это ужасно.
  • Разблокировать мьютекс ТАК СКОРО, КАК ВОЗМОЖНО после добавления элемента в очередь критические секции всегда должны быть как можно меньше. Это позволяет потребителям просыпаться, пока производитель спит.
  • Промывать cout (Я использовал endl чтобы сделать это), чтобы вывод был напечатан немедленно, это облегчает видеть, что происходит.
  • Распечатать "consumer" после пробуждение, потому что именно тогда потребитель потребляет, иначе вы получите вводящий в заблуждение вывод, показывающий, когда потребитель спит, а не когда он выполняет работу.

Основная проблема с вашим кодом заключалась в том, что производитель никогда не давал потребителям возможность запустить. Он добавил в очередь, поспал на секунду (все еще удерживая блокировку мьютекса), затем уведомил переменную условия (все еще удерживая мьютекс), затем очень быстро снял блокировку мьютекса и снова приобрел ее. Вероятно, вы видели, что потребительский поток получил уведомление, попытался получить блокировку мьютекса, обнаружил, что она все еще заблокирована (потоком производителя), и поэтому вернулся в спящий режим. Производитель никогда не выпускал мьютекс достаточно долго, чтобы другой поток мог его получить. Вы может быть смогли получить лучшие результаты, добавив std::this_thread::yield() в начале цикла производителя, до блокировка мьютекса, но алгоритмы, которые полагаются на yield() для правильности, как правило, нарушаются (и это действительно не имеет значения в моих тестах); Лучше исправить цикл производства, чтобы дать потребителям шанс проснуться и бежать.

Вот рабочий код:

#include <mutex>
#include <condition_variable>
#include <queue>
#include <string>
#include <iostream>
#include <thread>

// request
struct request
{
std::mutex mx;
std::condition_variable cv;
std::queue<std::string> q;
};

// producer
class producer
{
public:
producer(request & r) : request_(r) { }

void operator()();

private:
request & request_;
};

void
producer::operator()()
{
while (true) {
{
std::lock_guard<std::mutex> lock(request_.mx);
std::cout << "producer" << std::endl;
request_.q.push("something");
}
std::this_thread::sleep_for(std::chrono::seconds(1));
request_.cv.notify_one();
}
}

class consumer
{
public:
consumer(request & r) : request_(r) { }

void operator()();

private:
request & request_;
};

void
consumer::operator()()
{
while (true) {
std::unique_lock<std::mutex> lock(request_.mx);
request_.cv.wait (
lock, [this] {return !request_.q.empty();}
);
std::cout << "consumer" << std::endl;
request_.q.pop();
}
}

int
main()
{
// request
request request_;

// producer
std::thread producer_{producer(request_)};

// consumer
std::thread first_consumer_{consumer(request_)};
std::thread second_consumer_{consumer(request_)};

// join
producer_.join();
first_consumer_.join();
second_consumer_.join();
}
11

Другие решения

Вы должны разблокировать свой std::unique_lock перед звонком notify_one() в противном случае ваш цикл while будет пытаться заблокировать дважды в одном и том же потоке. Это справедливо как для производителя, так и для потребителя.

Тем не менее, я согласен с теми, кто говорит, что ваш вывод по запросу очень вводит в заблуждение Вы должны использовать композицию.
Если вы дадите мне 10 минут, я мог бы придумать что-то, что работает 🙂

0

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector