Skip to content

生产者与消费者

生产者与消费者问题,是一个经典的常规问题,其实也是线程问题。可以把生产者看成是一类线程,消费者看成是另一类线程,也就是C++11线程库中的 std::thread。因为生产者与消费者需要从共享的仓库中存数据或者取数据,涉及到对仓库的互斥访问,所以需要加锁,也就是 std::mutex。这里我们把仓库用一个任务队列TaskQueue进行封装,提供互斥锁、条件变量的基本数据成员,当然任务队列里面也就是基本操作,队列是不是满的,是不是空的,存数据与取数据等基本操作。

原理图

类图

禁止复制

在C++中,我们讲过两种语义,值语义与对象语义,特别是对于对象语义,表示的是不能进行复制或者赋值,所以我们可以将类的拷贝构造函数与赋值运算符函数删除或者设置为私有,但是除了这种方式之外,为了表示禁止复制的概念,我们还可以使用什么方法呢?因为具有对象语义的有很多,不可能每个类中都将拷贝构造函数与赋值运算符函数都设置为私有的,这样比较麻烦,所以可以想象其他的办法,使用继承。

cpp
class NoCopyable {
protected:
    NoCopyable() {}
    ~NoCopyable() {}
    NoCopyable(const NoCopyable &) = delete;
    NoCopyable &operator=(const NoCopyable &) = delete;
};

class Derived : public NoCopyable {};

void test() {
    Derived d1;
    Derived d2(d1); // error
    Derived d3;
    d3 = d1; // error
}

部分重要代码

cpp
TaskQueue::TaskQueue(size_t queSize) : _queSize(queSize), _que(), _mutex(), _notEmpty(), _notFull() {}

TaskQueue::~TaskQueue() {}

// 添加任务与获取任务
void TaskQueue::push(const int &value) {
    unique_lock<mutex> ul(_mutex);

    while (full()) {
        _notFull.wait(ul);
    }

    _que.push(value);
    _notEmpty.notify_one(); // 唤醒消费者
}

int TaskQueue::pop() {
    unique_lock<mutex> ul(_mutex);
    
    while (empty()) {
        _notEmpty.wait(ul);
    }

    int tmp = _que.front();
    _que.pop();
    _notFull.notify_one(); // 唤醒生产者

    return tmp;
}

// 判断空还是满
bool TaskQueue::empty() const { return 0 == _que.size(); }

bool TaskQueue::full() const { return _que.size() == _queSize; }