muduo::BlockingQueue、BoundedBlockingQueue分析

本文详细介绍了阻塞队列的概念及其实现原理,包括无界缓冲的BlockingQueue和有界缓冲的BoundedBlockingQueue。通过具体示例展示了如何使用这两种队列实现生产者消费者模型,并提供了测试代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在学习源码之前,先了解一个概念:有界缓冲和无界缓冲。

以生产者、消费者模型。
有界缓冲是指生产者在向仓库添加数据时要先判断仓库是否已满,如果已满则通知消费者来取走数据。消费者在消费时,先判断仓库是否已空,如果是则先通知生产者生产数据。

BlockingQueue

在无界缓冲中,生产者不用关心仓库是否已满,只需添加数据;消费者在判断仓库已空时要等待生产者的信号。这时只需要用一个信号量。

BlockingQueue就是这样的一个模型

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(); // wait morphing saves us
    // http://www.domaigne.com/blog/computing/condvars-signal-with-mutex-locked-or-not/
  }

#ifdef __GXX_EXPERIMENTAL_CXX0X__
  void put(T&& x)//右值
  {
    MutexLockGuard lock(mutex_);
    queue_.push_back(std::move(x));
    notEmpty_.notify();
  }
  // FIXME: emplace()
#endif

  T take()//消费数据
  {
    MutexLockGuard lock(mutex_);
    // always use a while-loop, due to spurious wakeup
    while (queue_.empty())//仓库已空
    {
      notEmpty_.wait();//等待生产者信号
    }
    assert(!queue_.empty());
#ifdef __GXX_EXPERIMENTAL_CXX0X__
    T front(std::move(queue_.front()));
#else
    T front(queue_.front());
#endif
    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_;//仓库
};

可以写个生产者消费者测试代码,有了这个BlockingQueue,不用自己在管理互斥量和信号量了。

#include <muduo/base/BlockingQueue.h>
#include <muduo/base/Thread.h>

#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>

#include <iostream>
using namespace muduo;

using namespace boost;

void Produce(shared_ptr<BlockingQueue<int> > queue)
{
    while(true)
    {
        int product=rand()%1000+1;
        std::cout<<"Produce: "<<product<<std::endl;
        queue->put(product);
        sleep(rand()%5);
    }

}
void Consome(shared_ptr<BlockingQueue<int> > queue)
{
    while(true)
    {
        int product=queue->take();
        std::cout<<"Consome: "<<product<<std::endl;
    }
}
int main()
{
    shared_ptr<BlockingQueue<int> > blockingQueue(new BlockingQueue<int>);
    Thread t1(boost::bind(Produce, blockingQueue));
    Thread t2(boost::bind(Consome, blockingQueue));

    t1.start();
    t2.start();

    t1.join();
    t2.join();

    return 0;
}

BoundedBlockingQueue

与BlockingQueue不同,BoundedBlockingQueue是有边界的,即仓库是有限的。这时仓库有四个状态:非空、已空;非满、已满。

当生产者生产时,会先判断仓库是否已满,如果是则等待仓库非满的信号;否则则向仓库添加货物,之后通知消费者仓库非空。

当消费者取货物时会先判断仓库是否为空,如果是则等待仓库非空信号;否则取走货物,通知生产者仓库非满。

这时候实现生产者消费者模型时需要2个信号量,一个是非空,表示消费者可以消费了;一个是非满,表示生产者可以生产了。源码如下:

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();//通知消费者仓库已经有货(非空)
  }

  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();
  }

  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_;
};

测试代码:

//boundedBlokcingQueue.cpp

#include <muduo/base/BoundedBlockingQueue.h>
#include <muduo/base/Thread.h>

#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>

#include <iostream>
using namespace muduo;

using namespace boost;

void Produce(shared_ptr<BoundedBlockingQueue<int> > queue)
{
    while(true)
    {
        int product=rand()%1000+1;
        std::cout<<"Produce: "<<product<<std::endl;
        queue->put(product);
        sleep(rand()%5);
    }

}
void Consome(shared_ptr<BoundedBlockingQueue<int> > queue)
{
    while(true)
    {
        int product=queue->take();
        std::cout<<"Consome: "<<product<<std::endl;
        sleep(rand()%5);
    }
}
int main()
{
    shared_ptr<BoundedBlockingQueue<int> > boundedBlockingQueue(new BoundedBlockingQueue<int>(5));
    Thread t1(boost::bind(Produce, boundedBlockingQueue));
    Thread t2(boost::bind(Consome, boundedBlockingQueue));

    t1.start();
    t2.start();

    t1.join();
    t2.join();

    return 0;
}
#include <muduo/net/TcpServer.h> #include <muduo/base/Logging.h> #include <boost/bind.hpp> #include <muduo/net/EventLoop.h> // 使用muduo开发回显服务器 class EchoServer { public: EchoServer(muduo::net::EventLoop* loop, const muduo::net::InetAddress& listenAddr); void start(); private: void onConnection(const muduo::net::TcpConnectionPtr& conn); void onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp time); muduo::net::TcpServer server_; }; EchoServer::EchoServer(muduo::net::EventLoop* loop, const muduo::net::InetAddress& listenAddr) : server_(loop, listenAddr, "EchoServer") { server_.setConnectionCallback( boost::bind(&EchoServer::onConnection, this, _1)); server_.setMessageCallback( boost::bind(&EchoServer::onMessage, this, _1, _2, _3)); } void EchoServer::start() { server_.start(); } void EchoServer::onConnection(const muduo::net::TcpConnectionPtr& conn) { LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> " << conn->localAddress().toIpPort() << " is " << (conn->connected() ? "UP" : "DOWN"); } void EchoServer::onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp time) { // 接收到所有的消息,然后回显 muduo::string msg(buf->retrieveAllAsString()); LOG_INFO << conn->name() << " echo " << msg.size() << " bytes, " << "data received at " << time.toString(); conn->send(msg); } int main() { LOG_INFO << "pid = " << getpid(); muduo::net::EventLoop loop; muduo::net::InetAddress listenAddr(8888); EchoServer server(&loop, listenAddr); server.start(); loop.loop(); } 这个代码应该怎么改?
最新发布
07-07
### muduo::net::Buffer 的用法及常见问题分析 #### 1. **muduo::net::Buffer 类概述** `muduo::net::Buffer` 是一个高效的内存缓冲区实现,旨在简化 TCP 数据传输过程中的 I/O 操作。它解决了传统网络库仅提供简单通知机制而不具备内置缓冲功能的问题[^1]。该类封装了一个动态增长的连续内存区域,能够自动扩展容量以适应不同大小的数据包。 核心特性包括但不限于以下几点: - 提供线程安全的操作接口。 - 支持零拷贝技术减少不必要的数据移动开销。 - 自动管理内部存储空间避免手动调整带来的复杂度。 具体而言,`Buffer` 主要由两部分构成:一是指向实际分配内存块起始位置的指针 `_bufferStart`;二是记录当前有效负载长度以及可用剩余空间的信息字段如 `readableBytes_`, `writableBytes_`. #### 2. **基本操作方法** ##### (a). 初始化与销毁 创建一个新的 `Buffer` 对象通常无需显式指定初始尺寸,默认情况下会预留一定数量的字节作为起点。析构函数则负责释放关联的所有资源确保无泄漏风险存在。 ```cpp // 构造函数实例化新对象 muduo::net::Buffer bufferInstance; ``` ##### (b). 添加数据到缓存尾部 通过追加方式向现有内容之后附加新的数据片段非常简便直观。 ```cpp const char message[] = "Hello Muduo!"; bufferInstance.append(message, sizeof(message)-1); // 不含结尾'\0' ``` 此处第二个参数表明待加入字符串的实际长度(排除终止符),从而精确控制目标范围边界条件[^2]. ##### (c). 获取可读取视图窗口 允许外部访问已填充完毕的部分以便执行进一步处理动作比如序列化解码等工作流程环节。 ```cpp size_t readableSize = bufferInstance.readableBytes(); const char* dataPtr = bufferInstance.peek(); std::string receivedData(dataPtr, readableSize); processIncomingMessage(receivedData); // 用户自定义消息处理器原型示意 ``` 上述代码段先查询有多少字节能够立即消费掉紧接着提取首地址形成临时副本传递给高层应用层逻辑单元继续运作下去[^3]. ##### (d). 移除已经消耗过的前缀成分 一旦确认某些数据已经被妥善处置过后就应该及时将其从队列前端剔除出去腾出更多地方接纳后续到来的新批次材料进来。 ```cpp bufferInstance.retrieve(readableSize); ``` 这一步骤本质上修改了内部索引标记使得原先占据的位置变得不可见但仍保留物理上的连续性特征不变样貌依旧保持整齐有序排列状态之中[^4]. #### 3. **常见问题解答** 问: 当频繁触发扩容行为时是否会引发显著性能下降? 答: 设计之初便充分考虑到这一点因此采用了指数级倍增策略即每次超出限额都会按照原规模翻番重新申请更大规格的一整片空白领域覆盖旧址之上进而摊薄长期平均成本达到最优平衡点附近徘徊浮动不定程度较小几乎可以忽略不计的程度范围内游走变化着[^5]. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值