muduo网络库源码复现笔记(六):base库的BlockingQueue.h和BoundedBlockingQueue.h

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类似,这里不再赘述了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值