我正在玩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函数时我得到了一个效果但是当我从一个线程调用它时没有任何反应? 谢谢
答案 0 :(得分:5)
您的示例代码遇到了其他人注意到的几个同步问题:
我建议在生产者和消费者之间使用条件变量进行同步。在这里查看生产者/消费者示例:http://en.cppreference.com/w/cpp/thread/condition_variable 从C ++ 11开始,它是标准库中的一个相当新的特性,并且从VS2012开始支持。在VS2012之前,您需要提升或使用Win32呼叫。
使用条件变量解决生产者/消费者问题很好,因为它几乎强制使用互斥锁来锁定共享数据,它提供了一种信令机制,让消费者知道某些东西已经准备好被消费,所以他们不会如此旋转(这总是在消费者的响应性和轮询队列的CPU使用率之间进行权衡)。它也是原子本身,这可以防止线程丢失信号的可能性,如此处所解释的那样有消耗的东西:https://en.wikipedia.org/wiki/Sleeping_barber_problem
简要介绍一下条件变量如何解决这个问题......
这会对您的代码产生以下影响:
我觉得线程对你来说是个新鲜事,所以我只能提供建议,看看其他人如何解决同步问题,因为很难解决这个问题。对具有多个线程和共享数据的环境中发生的事情的困惑通常会导致诸如死锁之类的问题。
答案 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);
带参数的线程构造函数
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));
}
另一个问题是关于您的互斥锁用法。这不是最好的。
为什么使用Signals2库中的互斥锁?只需使用Boost.Thread中的boost::mutex
并删除对Signals2库的未使用的依赖项。
使用RAII包装boost::lock_guard
代替直接lock/unlock
来电。
正如其他人所说,你应该锁定ProtectedStack
的所有成员。
样品:
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)
在尝试使用之前,您没有检查生产线程是否已执行。你也没有锁定大小/空/顶...如果容器正在更新,这是不安全的。