c ++中的消费者/生产者

时间:2012-02-22 14:00:25

标签: c++ c concurrency mutex producer-consumer

这是一个经典的c / p问题,其中一些线程产生数据而另一些线​​程读取数据。生产者和消费者都共享一个const大小的缓冲区。如果缓冲区为空,则消费者必须等待,如果已满,则生产者必须等待。我正在使用信号量来跟踪完整或空闲的队列。生产者将减少信号量,增加价值和增加填充的信号量信号量。所以我试图实现一个从生成器函数中获取一些数字的程序,然后打印出数字的平均值。通过将其视为生产者 - 消费者问题,我试图节省一些时间来执行程序。 generateNumber函数导致进程有一些延迟,所以我想创建一些生成数字的线程,并将它们放入队列中。然后运行main函数的“主线程”必须从队列中读取并找到总和然后平均。所以这就是我到目前为止所做的:

#include <cstdio> 
#include <cstdlib>
#include <time.h>
#include "Thread.h" 
#include <queue> 

int generateNumber() {
    int delayms = rand() / (float) RAND_MAX * 400.f + 200;
    int result = rand() / (float) RAND_MAX * 20;
    struct timespec ts;
    ts.tv_sec = 0;
    ts.tv_nsec = delayms * 1000000;
    nanosleep(&ts, NULL);
    return result; }


struct threadarg {
    Semaphore filled(0);
    Semaphore empty(n);
    std::queue<int> q; };


void* threadfunc(void *arg) {
    threadarg *targp = (threadarg *) arg;
    threadarg &targ = *targp;
    while (targ.empty.value() != 0) {
        int val = generateNumber();
        targ.empty.dec(); 
        q.push_back(val);
        targ.filled.inc(); }
}
int main(int argc, char **argv) {
    Thread consumer, producer;
    // read the command line arguments
    if (argc != 2) {
        printf("usage: %s [nums to average]\n", argv[0]);
        exit(1); }
    int n = atoi(argv[1]);
    // Seed random number generator
    srand(time(NULL));
}

我现在有点困惑,因为我不确定如何在消费者从队列中读取时(如果q不为空),如何创建生成数字的多个生成器线程(如果q未满)。我不确定在主要内容中应该包含什么内容。 同样在“Thread.h”中,您可以创建线程,互斥锁或信号量。该线程具有.run(threadFunc,arg),。join()等方法。可以锁定或解锁互斥锁。信号量方法都已在我的代码中使用。

5 个答案:

答案 0 :(得分:4)

您的队列未同步,因此多个制作人可以同时拨打push_back,或者在消费者呼叫pop_front的同时...这将会中断。

使这项工作的简单方法是使用一个线程安全的队列,它可以是你已经拥有的std::queue的一个包装器,还有一个互斥锁。

您可以首先添加一个互斥锁,并在转发到std::queue的每个呼叫周围锁定/解锁它 - 对于一个应该足够的消费者,对于您需要融合的多个消费者{{ 1}}和front()进入单个同步调用。

要让使用者在队列为空时阻止,您可以向包装器添加一个条件变量。

这应该足以让你在网上找到答案 - 以下示例代码。


pop_front()

template <typename T> class SynchronizedQueue { std::queue<T> queue_; std::mutex mutex_; std::condition_variable condvar_; typedef std::lock_guard<std::mutex> lock; typedef std::unique_lock<std::mutex> ulock; public: void push(T const &val) { lock l(mutex_); // prevents multiple pushes corrupting queue_ bool wake = queue_.empty(); // we may need to wake consumer queue_.push(val); if (wake) condvar_.notify_one(); } T pop() { ulock u(mutex_); while (queue_.empty()) condvar_.wait(u); // now queue_ is non-empty and we still have the lock T retval = queue_.front(); queue_.pop(); return retval; } }; 等替换为您的&#34; Thread.h&#34;给你。

答案 1 :(得分:1)

我要做的是:

  • 创建一个隐藏队列的数据类
  • 创建线程安全的访问器方法,用于将一段数据保存到q,并从q中删除一段数据(我会使用单个互斥锁,或者访问器的关键部分)
  • 处理消费者没有任何数据可以使用的情况(睡眠)
  • 处理q变得太满的情况,生产者需要放慢速度
  • 让线程在生成/消费时不断添加和删除

此外,请记住在每个线程中添加一个休眠,否则您将固定CPU并且不会让线程调度程序成为切换上下文并与其他线程/进程共享CPU的好地方。你不需要,但这是一个很好的做法。

答案 2 :(得分:0)

当像这样管理共享状态时,您需要一个条件变量和 一个互斥体。基本模式是一个函数:

ScopedLock l( theMutex );
while ( !conditionMet ) {
    theCondition.wait( theMutex );
}
doWhatever();
theCondition.notify();

在你的情况下,我可能会使条件变量和互斥量 实现队列的类的成员。要写, conditionMet将是!queue.full(),所以你最终会得到一些东西 像:

ScopedLock l( queue.myMutex );
while ( queue.full() ) {
    queue.myCondition.wait();
}
queue.insert( whatever );
queue.myCondition.notify();

并阅读:

ScopedLock l( queue.myMutex );
while ( queue.empty() ) {
    queue.myCondition.wait();
}
results = queue.extract();
queue.myCondition.notify();
return results;

根据线程界面,可能有两个notify 函数:通知一个(唤醒单个线程),并通知所有 (唤醒所有等待的线程);在这种情况下,你需要 通知所有(或者您需要两个条件变量,一个用于空间 写,一个用于读取的东西,每个函数等待一个, 但通知对方)。

答案 3 :(得分:0)

使用互斥锁保护队列访问,应该是它。一个'计算机科学101'有限的生产者 - 消费者队列需要两个信号量,(管理空闲/空数和生产者/消费者等待,正如你已经在做的那样),以及一个互斥/ futex / criticalSection来保护队列

我不知道如何用condvars替换信号量和互斥量有什么帮助。重点是什么?如何使用condvars实现有界的生产者 - 消费者队列,该condvars适用于具有多个生产者/消费者的所有平台?

答案 4 :(得分:-1)

#include<iostream>
#include<deque>
#include<mutex>
#include<chrono>
#include<condition_variable>
#include<thread>
using namespace std;
mutex mu,c_out;
condition_variable cv;
class Buffer
{
public:
    Buffer() {}
    void add(int ele)
    {
        unique_lock<mutex> ulock(mu);
        cv.wait(ulock,[this](){return q.size()<_size;});
        q.push_back(ele);
        mu.unlock();
        cv.notify_all();
        return;
    }
    int remove()
    {
     unique_lock<mutex> ulock(mu);
     cv.wait(ulock,[this](){return q.size()>0;});
     int v=q.back();
     q.pop_back();
     mu.unlock();
     cv.notify_all();
     return v;
    }
    int calculateAvarage()
    {
        int total=0;
        unique_lock<mutex> ulock(mu);
        cv.wait(ulock,[this](){return q.size()>0;});
        deque<int>::iterator it = q.begin();
        while (it != q.end())
        {
            total += *it;
            std::cout << ' ' << *it++;
        }
        return total/q.size();
    }
private:
    deque<int> q;
    const unsigned int _size=10;
};
class Producer
{
public:
    Producer(Buffer *_bf=NULL)
    {
        this->bf=_bf;
    }
    void Produce()
    {
        while(true)
        {
            int num=rand()%10;
            bf->add(num);
            c_out.lock();
            cout<<"Produced:"<<num<<"avarage:"<<bf->calculateAvarage()<<endl;
            this_thread::sleep_for(chrono::microseconds(5000));
            c_out.unlock();
        }
    }
private:
    Buffer *bf;
};
class Consumer
{
public:
    Consumer(Buffer *_bf=NULL)
    {
        this->bf=_bf;
    }
    void Consume()
    {
        while (true)
        {
            int num=bf->remove();
            c_out.lock();
            cout<<"Consumed:"<<num<<"avarage:"<<bf->calculateAvarage()<<endl;
            this_thread::sleep_for(chrono::milliseconds(5000));
            c_out.unlock();
        }
    }
private:
    Buffer *bf;
};
int main()
{
    Buffer b;
    Consumer c(&b);
    Producer p(&b);
    thread th1(&Producer::Produce,&p);
    thread th2(&Consumer::Consume,&c);
    th1.join();
    th2.join();
    return 0;
}

Buffer 类具有双倍队列,最大 Buffer 大小为 10。 它有两个功能来添加到队列和从队列中删除。 Buffer 类具有 calculateAvarage() 函数,该函数将计算添加或删除元素的平均时间。

还有两个类,一个是具有 buffwr 类指针的生产者和消费者。 我们在消费者类中有 Consume() ,在 Producer 类中有 Produce() 。 Consume()>>锁定缓冲区并检查缓冲区的大小是否为0,它将从缓冲区中删除并通知生产者并解锁。 Produce()>>锁定缓冲区并检查缓冲区的大小是否不是最大缓冲区大小,它将添加并通知消费者并解锁。