【问题标题】:C++ Producer Consumer stuck in deadlockC ++生产者消费者陷入僵局
【发布时间】:2023-04-01 22:09:02
【问题描述】:

我正在尝试创建一个生产者-消费者程序,消费者必须继续运行,直到所有生产者完成,然后消费队列中剩余的内容(如果还有剩余),然后结束。您可以在下面查看我的代码,我想我知道问题出在哪里(可能是死锁),但我不知道如何使其正常工作。

  #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 个,但没有一个回答我的问题。

【问题讨论】:

  • 用良好的抽象来隔离同步位通常是个好主意。您在这里寻找的是有界缓冲区。
  • 现在你偷偷地整合了我的更改,它仍然死锁吗?
  • 您的代码无法编译。发布Minimal, Complete, and Verifiable example
  • 我不想离题太远,但总的来说我们不想让算法吃掉所有的系统资源。我们应该考虑并设置一个合适的上限(如果合适,可以相当大)。但我的主要观点是有界缓冲区抽象隔离了所有锁定和等待。生产者所要做的就是制作物品并调用 push()。消费者所要做的就是调用 pop() 并处理这些项目。 (我还希望 OP 会查找有界缓冲区以查看它是什么。)
  • 我已经包含了最小、完整和可验证的示例。有一些简化,例如生产者中的条件不同(它应该由不同的类触发),但死锁(或其他一些问题)仍然存在。

标签: c++ multithreading producer-consumer condition-variable


【解决方案1】:

当人们将std::atomicstd::mutexstd::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();

【讨论】:

  • 我可能把你和变量名弄混了——订单和产品应该是一回事——产品。我已将您的建议纳入其中,该程序现在运行得更加灵活 - 有时会以僵局结束,有时会结束,但消费者不会消费这些产品。
  • 堆栈溢出并不是一个很好的交互式调试站点。如果 Maxim 已经解决了您最初的问题,那么请接受答案,如果您有其他问题,请稍后再回来。 (另外,请将示例恢复为原始形式。)
  • “当人们将 std::atomic 与 std::mutex 和 std::condition_variable 一起使用时,几乎 100% 的情况下会导致死锁”的声明。似乎过于笼统。不一定总是如此。如果您检查修订历史并阅读原始代码,则错误相当简单:条件变量条件中未检查终止标志。 atomic + mutex + condition_variable ==> 死锁对我来说似乎是一个过度声明。
猜你喜欢
  • 2019-08-19
  • 1970-01-01
  • 2018-08-26
  • 2018-07-15
  • 1970-01-01
  • 2015-06-16
  • 1970-01-01
  • 2012-03-12
相关资源
最近更新 更多