一、生产者---消费者模型
无界缓冲区与有界缓冲区的封装,本质就是生产者---消费者模型。生产者消费者模型一般有两种实现方式,可以利用信号量也可以利用条件变量实现,muduo库采用条件变量实现。
有界缓冲区是指生产者在向仓库添加数据时要先判断仓库是否已满,如果已满则通知消费者来取走数据;消费者在消费时,先判断仓库是否已空,如果是则通知生产者生产数据。
在无界缓冲中,生产者不用关心仓库是否已满,只需添加数据;消费者在判断仓库已空时要等待生产者的信号,这时只需用一个信号量。
多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。
二、BlockingQueue
BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。
BlockingQueue它是线程安全的,我们在外部调用它时无需加锁。它的类图如下:
该类是没有边界的,即在这个队列中,如果为空,是不能进行取走的操作,但是可以一直的往队列中进行添加
源代码:
#ifndef MUDUO_BASE_BLOCKINGQUEUE_H
#define MUDUO_BASE_BLOCKINGQUEUE_H
#include "muduo/base/Condition.h"
#include "muduo/base/Mutex.h"
#include <deque>
#include <assert.h>
//无界队列的实现
namespace muduo
{
template<typename T>
class BlockingQueue : noncopyable
{
public:
BlockingQueue()
: mutex_(), //先初始化互斥量
notEmpty_(mutex_), //再用互斥量初始化信号
queue_()
{
}
//生产产品(往阻塞队列(缓冲区)放任务)
void put(const T& x)
{
MutexLockGuard lock(mutex_); //先加上锁对队列进行保护,构造函数中调用lock,析构函数会自动调用unlock
queue_.push_back(x); //产品放进队列
notEmpty_.notify(); //每添加一个元素,就通知所有的线程(当前缓冲区不为空,可以来取任务了);
//队列不为空,通知消费者可以进行消费;通知等待的线程,实现线程同步
// http://www.domaigne.com/blog/computing/condvars-signal-with-mutex-locked-or-not/
}
void put(T&& x) //右值
{
MutexLockGuard lock(mutex_);
queue_.push_back(std::move(x));
notEmpty_.notify();
}
//消费产品(向缓冲区取任务)
T take()
{
MutexLockGuard lock(mutex_); //加锁保护队列(线程安全)
// always use a while-loop, due to spurious wakeup
while (queue_.empty()) //如果队列为空(仓库已空)
{
notEmpty_.wait(); //等待生产者信号(缓冲区为空则无法取任务)
}
assert(!queue_.empty()); //确保队列非空
T front(std::move(queue_.front())); //取出队首元素
queue_.pop_front(); //将队首元素弹出
return front; //返回队首元素
}
size_t size() const /*可能有多个线程访问所以需要保护*/
{
MutexLockGuard lock(mutex_); //加锁保护
return queue_.size(); //返回队列大小
}
private:
mutable MutexLock mutex_; //互斥锁(量);mutable表示可变的,修饰后可以改变const的特性
Condition notEmpty_ GUARDED_BY(mutex_); //条件变量(信号量)
std::deque<T> queue_ GUARDED_BY(mutex_); //仓库; 使用了deque<T>双端队列
};
} // namespace muduo
#endif // MUDUO_BASE_BLOCKINGQUEUE_H
三、BounderBlockingQueue
BoundBlockingQueue有界阻塞队列,实际上就是实现了一个循环队列,功能和上面的BlockingQueue都是一样的。
muduo库实现该队列实际上是内部把boost::circular_buffer类作为底层数据结构实现的,所以说这两个阻塞队列其实没啥区别,只是底层采用不同数据结构存储数据而已。它的类图如下:
与BlockingQueue不同,BounderBlockingQueue是有边界的,即仓库是有限的。该类的仓库有四个状态:非空、已空;非满、已满。
当生产者生产时,会先判断仓库是否已满,如果是则等待仓库非满的信号;否则向仓库添加货物,之后通知消费者仓库非空。
当消费者取货物时会先判断仓库是否为空,如果是则等待仓库非空信号;否则取走货物,通知生产者仓库非满。
该类实现生产者消费者模型需要2个信号量,一个是非空,表示消费者可以消费了;一个是非满,表示生产者可以生产了。
当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。
当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。
源代码:
#ifndef MUDUO_BASE_BOUNDEDBLOCKINGQUEUE_H
#define MUDUO_BASE_BOUNDEDBLOCKINGQUEUE_H
#include "muduo/base/Condition.h"
#include "muduo/base/Mutex.h"
#include <boost/circular_buffer.hpp>
#include <assert.h>
//有界队列(环形队列)
namespace muduo
{
template<typename T>
class BoundedBlockingQueue : noncopyable
{
public:
explicit BoundedBlockingQueue(int maxSize) //参数是队列的最大容量
: mutex_(),
notEmpty_(mutex_),
notFull_(mutex_),
queue_(maxSize)
{
}
void put(const T& x)
{
MutexLockGuard lock(mutex_);
while (queue_.full()) //仓库已满
{
notFull_.wait(); //等待非满信号,即消费者消费后会通知
}
assert(!queue_.full());
queue_.push_back(x); //添加一个元素
notEmpty_.notify(); //不为空的条件变量就通知线程,通知消费者仓库已经有货(非空)
}
void put(T&& x)
{
MutexLockGuard lock(mutex_);
while (queue_.full())
{
notFull_.wait();
}
assert(!queue_.full());
queue_.push_back(std::move(x));
notEmpty_.notify();
}
T take()
{
MutexLockGuard lock(mutex_);
while (queue_.empty()) //仓库已空
{
notEmpty_.wait(); //等待生产者向仓库添加货物
}
assert(!queue_.empty());
T front(std::move(queue_.front()));
queue_.pop_front(); //当删除(取走)队列中的元素的时候
notFull_.notify(); //不为满的条件变量就通知线程,通知生产者仓库已经非满了
return front;
}
bool empty() const
{
MutexLockGuard lock(mutex_);
return queue_.empty();
}
bool full() const
{
MutexLockGuard lock(mutex_);
return queue_.full();
}
size_t size() const
{
MutexLockGuard lock(mutex_);
return queue_.size();
}
size_t capacity() const //返回容量
{
MutexLockGuard lock(mutex_);
return queue_.capacity();
}
private:
mutable MutexLock mutex_;
Condition notEmpty_ GUARDED_BY(mutex_); //非空信号量,非空可读
Condition notFull_ GUARDED_BY(mutex_); //非满信号量,不满可写
boost::circular_buffer<T> queue_ GUARDED_BY(mutex_); //使用了boost库的环形缓冲区
};
} // namespace muduo
#endif // MUDUO_BASE_BOUNDEDBLOCKINGQUEUE_H