Muduo网络库简介
muduo 是一个基于 Reactor 模式的现代 C++ 网络库,作者陈硕。它采用非阻塞 IO 模型,基于事件驱动和回调,原生支持多核多线程,适合编写 Linux 服务端多线程网络应用程序。
muduo网络库的核心代码只有数千行,在网络编程技术学习的进阶阶段,muduo是一个非常值得学习的开源库。目前我也是刚刚开始学习这个网络库的源码,希望将这个学习过程记录下来。这个网络库的源码已经发布在GitHub上,可以点击这里阅读。目前Github上这份源码已经被作者用c++11重写,我学习的版本是没有使用c++11版本的。不过二者大同小异,核心思想是没有变化的。点这里可以看我的源代码,如果你对我之前的博客有兴趣,可以点击下面的连接:
muduo网络库源码复现笔记(一):base库的Timestamp.h
muduo网络库源码复现笔记(二):base库的Atomic.h
muduo网络库源码复现笔记(三):base库的Exception.h
muduo网络库源码复现笔记(四):base库的Thread.h和CurrentThread.h
muduo网络库源码复现笔记(五):base库的Mutex.h和Condition.h和CoutntDownLatch.h
BlockingQueue.h
BlockingQueue.h顾名思义,就是实现封装一个阻塞队列。熟悉操作系统编程的同学肯定了解生产者消费者问题,现在封装的这个阻塞队列便是解决这个问题的。下面我们看一下代码:
template <typename T>
class BlockingQueue : boost::noncopyable
{
public:
BlockingQueue()
: mutex_(),notEmpty_(mutex_),queue_()
{
}
void put(const T& x)
{
MutexLockGuard lock(mutex_);
queue_.push_back(x);
notEmpty_.notify();
}
T take()
{
MutexLockGuard lock(mutex_);
while(queue_.empty())
{
notEmpty_.wait();
}
assert(!queue_.empty());
T front(queue_.front());
queue_.pop_front();
return front;
}
size_t size() const
{
MutexLockGuard lock(mutex_);
return queue_.size();
}
private:
mutable MutexLock mutex_;
Condition notEmpty_;
std::deque<T> queue_;
};
私有成员
这个类由三个私有成员,mutex_是前面封装过的互斥量。条件量notEmpty有两个用途,当消费者进行消费时,若没有产品了,notEmpty可以用于阻塞;当生产者生产一个产品后,可以唤醒其他线程来消费。queue_是一个双向队列,作用是存储产品,生产者生产的产品数目不受限。
put()函数
put函数模拟了生产一个产品的过程,首先加锁,然后在队列里加入产品后通知其他线程消费。
take()函数
take函数模拟的是消费者消费的过程,加锁后首先检测队列中是否有产品,没有则等待,当有产品可以消费时取出。值得注意的是take函数里的while循环,有时候不留神会把它写成if,出现虚假唤醒的错误。所谓虚假唤醒,常见有两个原因,一种是条件变量的等待被信号中断。pthread 的条件变量等待 pthread_cond_wait 是使用阻塞的系统调用实现的(比如 Linux 上的 futex),这些阻塞的系统调用在进程被信号中断后,通常会中止阻塞、直接返回 EINTR 错误。而因为本线程拿到 EINTR 错误和重新调用 futex 等待之间,可能别的线程进行了操作,出现问题。第二种可能的原因是多核cpu中,pthread_cond_signal会唤醒多个等待的线程,由于线程调度的原因,被条件变量唤醒的线程在本线程内真正执行「加锁并返回」前,另一个线程插了进来,完整地进行了一套「拿锁、改条件、还锁」的操作。
BoundedBlockingQueue.h
BoundedBlockingQueue.h与BlockingQueue.h大同小异,不同在于Bounded BlockingQueue的生产者队列有上限。
template<typename T>
class BoundedBlockingQueue : boost::noncopyable
{
public:
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();
}
T take()
{
MutexLockGuard lock(mutex_);
while(queue_.empty())
{
notEmpty_.wait();
}
assert(!queue_.empty());
T front(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();
}
bool size() const
{
MutexLockGuard lock(mutex_);
return queue_.size();
}
bool capacity() const
{
MutexLockGuard lock(mutex_);
return queue_.capacity();
}
private:
MutexLock mutex_;
Condition notEmpty_;
Condition notFull_;
boost::circular_buffer<T> queue_;
};
私有成员
mutex_是前面封装的锁。notEmpty有两个作用,一是在消费者消费时检查是否有产品可以消费,没有则等待;而是在生产者生产之后唤醒其他线程来消费;notFull有两个作用,一是生产者在生产者生产时判定队列是否已满;而是消费者消费完之后唤醒生产者来生产;queue_是一个boost::circular_buffer,下面详细讲述;
boost::circular_buffer
circular_buffer是用一块连续内存保存数据,元素的下标从0到n - 1依次增大(begin处为0, end - 1处为n - 1)。如果达到容量上限,继续push_back方法压入元素时,原来begin处的元素就会被覆盖,原来begin + 1处的元素成为新的begin.
其他函数
其他函数与Blocking类似,这里不再赘述了。