muduo网络库:使用无界缓冲区 BlockingQueue<T>设计生产者消费者模式

本文介绍了muduo库中的BlockingQueue和BoundedBlockingQueue。BlockingQueue是加锁的deque,通过条件变量实现同步;BoundedBlockingQueue是固定大小的BlockingQueue。还讲述了使用BlockingQueue设计生产者消费者,生产者由主线程向队列塞东西,消费者由多线程从队列取东西,muduo库封装调用使接口更灵活高效。

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

BlockingQueueBoundedBlockingQueue 是 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,这是为了防止在真正进入等待状态之前别的线程有可能设置该条件变量而产生竞争。

通过 puttake 函数的封装可以看到,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 库的源码了,在原标准库和线程库的基础上再次进行了封装调用,使得各个接口变得更加灵活和高效,免去了大量的重复性的代码。陈硕确实厉害,期待自己成为像他一样的大牛!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值