多线程程序生产者/消费者[boost]

时间:2012-10-23 13:37:58

标签: c++ multithreading boost-thread boost-mutex

我正在玩boost库和C ++。我想创建一个包含生产者,conumer和堆栈的多线程程序。 procuder填充堆栈,消费者从堆栈中删除items(int)。一切正常(pop,push,mutex)但是当我把pop / push winthin称为线程时,我没有任何效果

我制作了这个简单的代码:

#include "stdafx.h"
#include <stack>
#include <iostream>
#include <algorithm>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/date_time.hpp> 
#include <boost/signals2/mutex.hpp>
#include <ctime>

using namespace std;

/ *
* this class reprents a stack which is proteced by mutex
* Pop and push are executed by one thread each time.
*/
class ProtectedStack{
private : 
stack<int> m_Stack;
boost::signals2::mutex m;

public : 
ProtectedStack(){
}
ProtectedStack(const ProtectedStack & p){

}
void push(int x){
    m.lock();
    m_Stack.push(x);
    m.unlock();
}

void pop(){
    m.lock();
    //return m_Stack.top();
    if(!m_Stack.empty())
        m_Stack.pop();
    m.unlock(); 
}
int size(){
    return m_Stack.size();
}
bool isEmpty(){
    return m_Stack.empty();
}
int top(){
    return m_Stack.top();
}
};

/*
*The producer is the class that fills the stack. It encapsulate the thread object 
*/

class Producer{
public:
Producer(int number ){
    //create thread here but don't start here
m_Number=number;


}
void fillStack (ProtectedStack& s ) {
    int object = 3; //random value
    s.push(object);
    //cout<<"push object\n";
}

void produce (ProtectedStack & s){
    //call fill within a thread 
    m_Thread = boost::thread(&Producer::fillStack,this, s);  
}

 private :
int m_Number;
boost::thread m_Thread;

};


/* The consumer will consume the products produced by the producer */ 

class Consumer {
private : 
int m_Number;
boost::thread m_Thread;
public:
Consumer(int n){
    m_Number = n;
}

void remove(ProtectedStack &s ) {

     if(s.isEmpty()){ // if the stack is empty sleep and wait for the producer      to fill the stack
        //cout<<"stack is empty\n";
        boost::posix_time::seconds workTime(1); 
        boost::this_thread::sleep(workTime);
     }
     else{
        s.pop(); //pop it
        //cout<<"pop object\n";

     }

}

void consume (ProtectedStack & s){
    //call remove within a thread 
    m_Thread = boost::thread(&Consumer::remove, this, s);  
}

};


int main(int argc, char* argv[])  
{  



ProtectedStack s;


    Producer p(0);
    p.produce(s);

    Producer p2(1);
    p2.produce(s);

    cout<<"size after production "<<s.size()<<endl;
    Consumer c(0);
    c.consume(s);
    Consumer c2(1);
    c2.consume(s);
    cout<<"size after consumption  "<<s.size()<<endl;

getchar();
return 0;  
}  

在VC ++ 2010 / win7中运行之后 我有 : 0 0

你能帮我理解为什么当我从主要调用fillStack函数时我得到了一个效果但是当我从一个线程调用它时没有任何反应? 谢谢

3 个答案:

答案 0 :(得分:5)

您的示例代码遇到了其他人注意到的几个同步问题:

  • 对ProtectedStack的某些成员的调用缺少锁定。
  • 主线程可以在不允许工作线程加入的情况下退出。
  • 生产者和消费者不会像你期望的那样循环。生产者应该(当他们可以)生产时,消费者应该继续消费,因为新的元素被推到堆叠上。
  • 主要线程上的
  • cout可能会在生产者或消费者有机会工作之前完成。

我建议在生产者和消费者之间使用条件变量进行同步。在这里查看生产者/消费者示例:http://en.cppreference.com/w/cpp/thread/condition_variable 从C ++ 11开始,它是标准库中的一个相当新的特性,并且从VS2012开始支持。在VS2012之前,您需要提升或使用Win32呼叫。

使用条件变量解决生产者/消费者问题很好,因为它几乎强制使用互斥锁来锁定共享数据,它提供了一种信令机制,让消费者知道某些东西已经准备好被消费,所以他们不会如此旋转(这总是在消费者的响应性和轮询队列的CPU使用率之间进行权衡)。它也是原子本身,这可以防止线程丢失信号的可能性,如此处所解释的那样有消耗的东西:https://en.wikipedia.org/wiki/Sleeping_barber_problem

简要介绍一下条件变量如何解决这个问题......

  • 生产者在没有拥有互斥锁的情况下在其线程上执行所有耗时的活动。
  • 生产者锁定互斥锁,将其生成的项目添加到全局数据结构(可能是某种类型的队列),放开互斥锁并发出信号通知单个消费者 - 按顺序。
  • 正在等待条件变量的使用者自动重新获取互斥锁,将该项目从队列中移除并对其进行一些处理。在此期间,制作人已经在制作新项目,但必须等到消费者完成才能将项目排队。

这会对您的代码产生以下影响:

  • 不再需要ProtectedStack,正常的堆栈/队列数据结构也可以。
  • 如果使用足够新的编译器,则无需提升 - 删除构建依赖项总是一件好事。

我觉得线程对你来说是个新鲜事,所以我只能提供建议,看看其他人如何解决同步问题,因为很难解决这个问题。对具有多个线程和共享数据的环境中发生的事情的困惑通常会导致诸如死锁之类的问题。

答案 1 :(得分:2)

您的代码的主要问题是您的线程未同步。 请记住,默认情况下,线程执行不是有序的,并且没有排序,因此消费者线程实际上可以(并且在您的特定情况下)在任何生成器线程生成任何数据之前完成

为确保消费者在生产者完成其工作后运行,您需要在生产者线程上使用thread::join()函数,它将停止主线程执行,直到生产者退出:

// Start producers
...

p.m_Thread.join();  // Wait p to complete
p2.m_Thread.join(); // Wait p2 to complete

// Start consumers
...

这样做可以解决问题,但这对于典型的生产者 - 消费者用例来说可能并不好。

要实现更有用的案例,您需要修复消费者功能。 您的消费者函数实际上并不等待生成的数据,如果堆栈为空则它将退出,如果尚未生成数据,则不会消耗任何数据。

应该是这样的:

void remove(ProtectedStack &s)
{
   // Place your actual exit condition here,
   // e.g. count of consumed elements or some event
   // raised by producers meaning no more data available etc.
   // For testing/educational purpose it can be just while(true)
   while(!_some_exit_condition_)
   {
      if(s.isEmpty())
      {
          // Second sleeping is too big, use milliseconds instead
          boost::posix_time::milliseconds workTime(1); 
          boost::this_thread::sleep(workTime);               
      }               
      else
      {
         s.pop();
      }
   }
} 

另一个问题是thread构造函数使用错误:

m_Thread = boost::thread(&Producer::fillStack, this, s);  

引自Boost.Thread documentation

  

带参数的线程构造函数

     

template <class F,class A1,class A2,...>   thread(F f,A1 a1,A2 a2,...);

     

前提条件:   F,每个An必须可复制或移动。

     

效果:   好像thread(boost::bind(f,a1,a2,...))。因此, f和每个被复制到   内部存储以供新线程访问

这意味着您的每个线程都会收到自己的s副本,并且所有修改都不会应用于s,而是应用于本地线程副本。将值传递给函数参数时,情况也是如此。您需要通过引用传递s对象 - 使用boost::ref

void produce(ProtectedStack& s)
{
   m_Thread = boost::thread(&Producer::fillStack, this, boost::ref(s));
}

void consume(ProtectedStack& s)
{
   m_Thread = boost::thread(&Consumer::remove, this, boost::ref(s));
}  

另一个问题是关于您的互斥锁用法。这不是最好的。

  1. 为什么使用Signals2库中的互斥锁?只需使用Boost.Thread中的boost::mutex并删除对Signals2库的未使用的依赖项。

  2. 使用RAII包装boost::lock_guard代替直接lock/unlock来电。

  3. 正如其他人所说,你应该锁定ProtectedStack的所有成员。

  4. 样品:

    boost::mutex m;
    
    void push(int x)
    { 
       boost::lock_guard<boost::mutex> lock(m);
       m_Stack.push(x);
    } 
    
    void pop()
    {
       boost::lock_guard<boost::mutex> lock(m);
       if(!m_Stack.empty()) m_Stack.pop();
    }              
    
    int size()
    {
       boost::lock_guard<boost::mutex> lock(m);
       return m_Stack.size();
    }
    
    bool isEmpty()
    {
       boost::lock_guard<boost::mutex> lock(m);
       return m_Stack.empty();
    }
    
    int top()
    {
       boost::lock_guard<boost::mutex> lock(m);
       return m_Stack.top();
    }
    

答案 2 :(得分:1)

在尝试使用之前,您没有检查生产线程是否已执行。你也没有锁定大小/空/顶...如果容器正在更新,这是不安全的。