C ++ продюсер Потребитель застрял в тупике - PullRequest
0 голосов
/ 27 марта 2019

Я пытаюсь создать программу «производитель-потребитель», в которой потребители должны продолжать работать до тех пор, пока все производители не будут завершены, затем использовать то, что осталось в очереди (если что-то осталось), а затем завершиться. Вы можете проверить мой код ниже, я думаю, я знаю, где проблема (возможно, тупик), но я не знаю, как заставить его работать должным образом.

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

  using namespace std;

  class Company{
    public:
        Company() : producers_done(false) {}
        void start(int n_producers, int n_consumers); // start customer&producer threads
        void stop(); // join all threads
        void consumer();
        void producer();
        /* some other stuff */
    private:
        condition_variable cond;
        mutex mut;
        bool producers_done;
        queue<int> products;
        vector<thread> producers_threads;
        vector<thread> consumers_threads;
        /* some other stuff */
  };

void Company::consumer(){
    while(!products.empty()){
        unique_lock<mutex> lock(mut);
        while(products.empty() && !producers_done){
            cond.wait(lock); // <- I think this is where the deadlock happens
        }
        if (products.empty()){
            break;
        }
        products.pop();
        cout << "Removed product " << products.size() << endl;
    }
}

void Company::producer(){
    while(true){
        if((rand()%10) == 0){
          break;
        }
        unique_lock<mutex> lock(mut);
        products.push(1);
        cout << "Added product " << products.size() << endl;
        cond.notify_one();
    }
}

void Company::stop(){
    for(auto &producer_thread : producers_threads){
        producer_thread.join();
    }
    unique_lock<mutex> lock(mut);
    producers_done = true;
    cout << "producers done" << endl;
    cond.notify_all();
    for(auto &consumer_thread : consumers_threads){
        consumer_thread.join();
    }
    cout << "consumers done" << endl;
}

void Company::start(int n_producers, int n_consumers){
  for(int i = 0; i<n_producers; ++i){
    producers_threads.push_back(thread(&Company::producer, this));
  }

  for(int i = 0; i<n_consumers; ++i){
    consumers_threads.push_back(thread(&Company::consumer, this));
  }
}

int main(){
  Company c;
  c.start(2, 2);
  c.stop();

  return true;
}

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

1 Ответ

2 голосов
/ 27 марта 2019

Когда люди используют std::atomic вместе с std::mutex и std::condition_variable, это приводит к тупику почти в 100% случаев.Это связано с тем, что изменения этой атомарной переменной не защищены мьютексом, и, следовательно, уведомления переменной состояния теряются при обновлении этой переменной после блокировки мьютекса, но до ожидания переменной условия в получателе.

Исправление будетне использовать std::atomic и только изменять и читать producers_done, пока удерживается мьютекс.Например:

void Company::consumer(){
    for(;;){
        unique_lock<mutex> lock(mut);
        while(products.empty() && !producers_done)
            cond.wait(lock);
        if(products.empty())
            break;
        orders.pop();
    }   
}

Другая ошибка в коде заключается в том, что в while(!products.empty()) он вызывает products.empty() без удержания мьютекса, что приводит к состоянию гонки.


Следующая ошибка - блокировка мьютекса во время ожидания завершения потоков потребителя.Исправить:

{
    unique_lock<mutex> lock(mut);
    producers_done = true;
    // mutex gets unlocked here.
}
cond.notify_all();

for(auto &consumer_thread : consumers_threads)
    consumer_thread.join();
...