BlockingQueue
和 BoundedBlockingQueue
是 muduo 库设计的两个类,其中BlockingQueue
作为无界缓冲区,BoundedBlockingQueue
作为有界缓冲区。陈硕的muduo库是开源的,感兴趣的可以自己找找代码看一下,下面对二者进行简单的介绍。
BlockingQueue 是一种加锁的 deque
不知道我这么评价陈硕写的 BlockingQueue
合不合理,BlockingQueue
底层的数据结构就是 deque
,我们将数据缓冲都放在BlockingQueue
的数据成员 deque
中。除此之外,为了保证缓冲区同步,BlockingQueue
还添加了 MutexLock
类成员 mutex_
,通过 mutex
我们可以在 put 和 take 的时候保持动作同步。下面先看看 BlockingQueue
的数据成员:
private:
mutable MutexLock mutex_;
Condition notEmpty_;
std::deque<T> queue_;
这里面 Condition
也是个 muduo库中的类。说到具体代码我再说它。这里面就这三个类成员变量。
接下来,看一看构造函数:
BlockingQueue(): mutex_(), notEmpty_(mutex_),queue_() { }
构造函数通过初始化列表的形式对三个成员进行了初始化。没啥好说的。接下来看 put
函数:
void put(const T& x)
{
MutexLockGuard lock(mutex_);
queue_.push_back(x);
notEmpty_.notify(); // TODO: move outside of lock
}
该函数是为了向 queue
中存入一个数据,首先先通过 lock
进行加锁,作用域在{ }
之中,通过锁住互斥元,防止多个线程同时对queue
进行操作,接下来是设置条件变量,即使得事件发生,这样等待该事件的线程将不再阻塞,类Condition
就是一个条件变量。其实Condition.notify()
的底层实现就是 pthread_cond_broadcast
函数。
接下来是 take
:
T take()
{
MutexLockGuard lock(mutex_);
// always use a while-loop, due to spurious wakeup
while (queue_.empty())
{
notEmpty_.wait();
}
assert(!queue_.empty());
T front(queue_.front());
queue_.pop_front();
return front;
}
在take之前首先进行 lock 加锁,然后判断empty是不是为空,如果为空的话,那么进行wait()
操作,wait()
操作的底层是pthread_cond_wait
用来等待条件变量被设置,值得注意的是这两个等待调用需要一个已经上锁的互斥体mutex
,这是为了防止在真正进入等待状态之前别的线程有可能设置该条件变量而产生竞争。
通过 put
和 take
函数的封装可以看到,BlockingQueue
类就是加锁的 deque
,然后利用 Condition
这一条件变量类进行 empty() 和 !empty() 之间的状态切换。
BoundedBlockingQueue 是一种固定了大小的 BlockingQueue
直接看源码吧。和BlockingQueue十分相近。
// Use of this source code is governed by a BSD-style license
// that can be found in the License file.
//
// Author: Shuo Chen (chenshuo at chenshuo dot com)
#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 <boost/noncopyable.hpp>
#include <assert.h>
namespace muduo
{
template<typename T>
class BoundedBlockingQueue : boost::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(); // TODO: move outside of lock
}
T take()
{
MutexLockGuard lock(mutex_);
while (queue_.empty())
{
notEmpty_.wait();
}
assert(!queue_.empty());
T front(queue_.front());
queue_.pop_front();
notFull_.notify(); // TODO: move outside of lock
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_;
Condition notFull_;
boost::circular_buffer<T> queue_;
};
}
#endif // MUDUO_BASE_BOUNDEDBLOCKINGQUEUE_H
使用 BlockingQueue 设计生产者消费者
首先我们考虑,生产者就是往 BlockingQueue
塞东西,干这件事可以让主线程来干,反正就是塞嘛!又不用考虑什么截止条件( BlockingQueue 是无界缓冲区);接下来,消费者就是将 BlockingQueue
中的东西拿出来,我们可以让很多人拿,这件事就交给多个线程进行操作。下面来看设计的代码:
#include <muduo/base/BlockingQueue.h>
#include <muduo/base/CountDownLatch.h>
#include <muduo/base/Thread.h>
#include <boost/bind.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <string>
#include <stdio.h>
//设计一个测试类以供测试
class Test
{
public:
Test(int numThreads)
: latch_(numThreads),
threads_(numThreads)
{
for (int i = 0; i < numThreads; ++i)
{
char name[32];
snprintf(name, sizeof name, "work thread %d", i);
threads_.push_back(new muduo::Thread(
boost::bind(&Test::threadFunc, this), muduo::string(name)));
}
//对每一个 Thread* 执行start
for_each(threads_.begin(), threads_.end(), boost::bind(&muduo::Thread::start, _1));
}
void run(int times)
{
printf("waiting for count down latch\n"); //主线程等待latch_降为0,当降为0时wait()函数将不再阻塞
latch_.wait();
printf("all threads started\n");
for (int i = 0; i < times; ++i)
{
char buf[32];
snprintf(buf, sizeof buf, "hello %d", i);
queue_.put(buf);
printf("tid=%d, put data = %s, size = %zd\n", muduo::CurrentThread::tid(), buf, queue_.size());
}
}
void joinAll()
{
for (size_t i = 0; i < threads_.size(); ++i)
{
queue_.put("stop");
}
for_each(threads_.begin(), threads_.end(), boost::bind(&muduo::Thread::join, _1));
}
private:
void threadFunc()
{
printf("tid=%d, %s started\n",
muduo::CurrentThread::tid(),
muduo::CurrentThread::name());
latch_.countDown();
bool running = true;
while (running)
{
std::string d(queue_.take());
printf("tid=%d, get data = %s, size = %zd\n", muduo::CurrentThread::tid(), d.c_str(), queue_.size());
running = (d != "stop");
}
printf("tid=%d, %s stopped\n",
muduo::CurrentThread::tid(),
muduo::CurrentThread::name());
}
muduo::BlockingQueue<std::string> queue_; //无界缓冲队列
muduo::CountDownLatch latch_; //条件变量
boost::ptr_vector<muduo::Thread> threads_; //指针容器,盛放线程指针
};
int main()
{
printf("pid=%d, tid=%d\n", ::getpid(), muduo::CurrentThread::tid());
Test t(5); //创建5个线程,并都拿东西
t.run(100); //run 塞东西
t.joinAll(); //对每个线程进行 join 操作,销毁线程
printf("number of created threads %d\n", muduo::Thread::numCreated());
}
我把代码都加上了注释,重点的地方都一一解释了,有心人应该会很明白的。
总结
看了一部分 muduo 库的源码了,在原标准库和线程库的基础上再次进行了封装调用,使得各个接口变得更加灵活和高效,免去了大量的重复性的代码。陈硕确实厉害,期待自己成为像他一样的大牛!