Muduo网络库源码剖析 | 线程池 ThreadPool的设计与实现

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/ZYZMZM_/article/details/99249571


线程池本质就是一个生产者-消费者模型,它维护一个线程队列和任务队列。一旦任务队列当中有任务,相当于生产者生产了东西,就唤醒线程队列中的线程来执行这些任务。那么,这些线程就相当于消费者线程。

Muduo库的线程数目属于启动时配置当线程池启动时,线程数目就已经固定下来。

由于Muduo库是基于对象编程的设计理念,所以Muduo库的任务队列中存放的都是回调函数,线程池的另外一种实现方式就是将Muduo库中任务队列换成一个taskbase的基类指针,通过虚函数多态来实现对任务的动态调用,但是如果是这样设计的话,Muduo库就必须提供一个taskbase的基类,外部的使用者就必须继承该基类才能够使用Muduo库。


ThreadPool.h

class ThreadPool : noncopyable
{
 public:
  typedef std::function<void ()> Task;

  explicit ThreadPool(const string& nameArg = string("ThreadPool"));
  ~ThreadPool();

  // Must be called before start().
  /* 设置任务队列允许存放的最大任务数量 */
  void setMaxQueueSize(int maxSize) { maxQueueSize_ = maxSize; }

  /* 设置线程执行前的回调函数 */
  void setThreadInitCallback(const Task& cb)
  { threadInitCallback_ = cb; }

  /* 启动线程池 */
  void start(int numThreads);

  /* 关闭线程池 */
  void stop();

  /* 获取线程池的名称 */
  const string& name() const
  { return name_; }

  /* 获取任务队列的大小 */
  size_t queueSize() const;

  /* 运行任务,往线程池当中的任务队列添加任务 */
  void run(Task f);

 private:
  /* 判满 */
  bool isFull() const REQUIRES(mutex_);

  /* 线程池中线程的执行函数 */
  void runInThread();

  /* 从任务队列首部获取队列 */
  Task take();

  /* 与条件变量配合使用的互斥锁 */
  mutable MutexLock mutex_;

  /* 任务队列queue_不为空了,有任务可以执行了,进而唤醒等待的线程 */
  Condition notEmpty_ GUARDED_BY(mutex_);

  /* 任务队列queue_不满了,有空间可以使用了,进而唤醒等待的线程 */
  Condition notFull_ GUARDED_BY(mutex_);

  /* 线程池的名称 */
  string name_;

  /* 线程执行前的回调函数,即线程初始化函数 */
  Task threadInitCallback_;
  
  /* 线程指针数组 */
  std::vector<std::unique_ptr<muduo::Thread>> threads_;

  /* 任务队列 */
  std::deque<Task> queue_ GUARDED_BY(mutex_);

  /* 任务队列允许存放的最大任务数量 */
  size_t maxQueueSize_;

  /* 线程池的运行状态 */
  bool running_;
};

ThreadPool.cc

/**************************************************************
 * Date:2019-08-01
 * Description : 构造函数对成员变量进行初始化,互斥锁条件变
 * 量的构造,任务队列的默认任务数量为0,设置线程池运行状态
 * 为false。
 ***************************************************************/
ThreadPool::ThreadPool(const string& nameArg)
  : mutex_(),
    notEmpty_(mutex_),
    notFull_(mutex_),
    name_(nameArg),
    maxQueueSize_(0),
    running_(false)
{
}

/**************************************************************
 * Date:2019-08-01
 * Description : 析构函数首先要进行线程池的状态判断,若是处于
 * 运行状态,那么需要关闭线程,否则不需要做任何操作。
 ***************************************************************/
ThreadPool::~ThreadPool()
{
  /* 如果线程池在运行,那就要进行内存处理,在stop()函数中执行 */
  if (running_)
  {
    stop();
  }
}

/**************************************************************
 * Date:2019-08-01
 * Description : start函数根据参数设置线程池中线程的数量、
 * 状态,同时设置线程的回调函数,最后通过start()创建线程。
 ***************************************************************/
void ThreadPool::start(int numThreads)
{
  assert(threads_.empty());

  /* 将线程的状态设置为启动状态 */
  running_ = true;

  /* 给线程池预留numThreads个空间 */
  threads_.reserve(numThreads);

  /* 在线程池中创建numThreads个线程,无任务时进入睡眠 */
  for (int i = 0; i < numThreads; ++i)
  {
    char id[32];
    snprintf(id, sizeof id, "%d", i+1);

	/* 
	 * runInThread是每个线程的线程运行函数,线程未执行任务
	 * 情况下会阻塞 
	 */
    threads_.emplace_back(new muduo::Thread(
          std::bind(&ThreadPool::runInThread, this), name_+id));

    /* 启动线程 */
    threads_[i]->start();
  }
  
  if (numThreads == 0 && threadInitCallback_)
  {
    threadInitCallback_();
  }
}


/**************************************************************
 * Date:2019-08-01
 * Description : stop函数,将线程池的状态修改为停止,同时
 * 等待所有的线程结束,
 ***************************************************************/
void ThreadPool::stop()
{
  {
  MutexLockGuard lock(mutex_);

  /* 将线程池的状态设为停止状态 */
  
  running_ = false;
  /* 让阻塞在notEmpty contition上的所有线程执行完毕 */
  notEmpty_.notifyAll();
  }

  for (auto& thr : threads_)
  {
    /* 对每个线程调用join,实际是调用pthread_join,回收线程资源 */
    thr->join();
  }
}

/*****************************************************
 * Date:2019-08-01
 * Description : queueSize()用来获取任务队列的长度
 *****************************************************/
size_t ThreadPool::queueSize() const
{
  MutexLockGuard lock(mutex_);
  return queue_.size();
}

/**************************************************************
 * Date:2019-08-01
 * Description : run函数将外部的任务放入任务队列,同时在放入
 * 任务队列时判断任务队列是否已满,如果已满,就阻塞等待,直到
 * 任务队列有空余的空间。
 ***************************************************************/
void ThreadPool::run(Task task)
{
  /* 线程池为空,那么直接由当前线程来执行任务 */
  if (threads_.empty())
  {
    task();
  }
  else
  {
    MutexLockGuard lock(mutex_);
	/* 若任务队列已满,那么循环等待,直到有空余空间 */
    while (isFull())
    {
      notFull_.wait();
    }
    assert(!isFull());
	
    /* 将task加入线程池的任务队列,此操作保证任务队列是不满的 */
    queue_.push_back(std::move(task));

	/* 通知某个等待从queue_中取task的线程 */
    notEmpty_.notify();
  }
}

/**************************************************************
 * Date:2019-08-01
 * Description : take函数,线程从线程池中取任务,如果任务队列
 * 任务数量为空,那么就阻塞等待,直到有任务进入任务队列。
 *
 * take函数是每个线程都执行的,需要考虑线程安全,即多线程下
 * 取任务的线程安全性,只能串行化
 ***************************************************************/
ThreadPool::Task ThreadPool::take()
{
  /* 使用锁来保证只有一个线程进入,防止惊群效应 */
  MutexLockGuard lock(mutex_);

  /**
   * 若任务队列为空,去循环等待直到任务队列不为空。
   * 在wait被唤醒后,还需要在while中去检查条件,这是为了防止“惊群效应”。
   * 如果是多个线程都在等待这个条件,而同时只能有一个线程进行处理,此时
   * 就必须要再次条件判断,以使只有一个线程进入临界区处理。
   * (解锁、等待唤醒、加锁)
   */  
  while (queue_.empty() && running_)
  {
    notEmpty_.wait();
  }
  Task task;
  
  if (!queue_.empty())
  {
    /* 如果任务队列不为空,那么从队列头部取出任务 */
    task = queue_.front();
	
	/* 取出任务后将该任务从队列头部弹出 */
    queue_.pop_front();
	
    if (maxQueueSize_ > 0)
    {
      /* 
       * 任务取出后任务队列肯定是不满的,这时便可以唤醒未
       * 满锁,外部可以通过run来添加新的任务了。
       */
      notFull_.notify();
    }
  }
  return task;
}

bool ThreadPool::isFull() const
{
  /**
   * 调用确保被使用线程锁住,因为isFull函数不是一个线程安全
   * 的函数,外部调用要加锁 
   */
  mutex_.assertLocked();
  return maxQueueSize_ > 0 && queue_.size() >= maxQueueSize_;
}

/**************************************************************
 * Date:2019-08-01
 * Description : runInThread()是线程运行函数,无任务时都会阻
 * 塞在take(),有任务时会争互斥锁。
 ***************************************************************/
void ThreadPool::runInThread()
{
  try
  {
    if (threadInitCallback_)
    {
      /* 每个线程运行前执行回调函数,线程初始化函数 */
      threadInitCallback_();
    }
	/* 线程池处于启动状态,会一直循环下去 */
    while (running_)
    {
      /* 从任务队列里取出任务,无任务会阻塞 */
      Task task(take());
	
      if (task)
      {
        /* 如果取出了任务,那么执行该任务 */
        task();
      }
    }
  }
  catch (const Exception& ex)
  {
    fprintf(stderr, "exception caught in ThreadPool %s\n", name_.c_str());
    fprintf(stderr, "reason: %s\n", ex.what());
    fprintf(stderr, "stack trace: %s\n", ex.stackTrace());
    abort();
  }
  catch (const std::exception& ex)
  {
    fprintf(stderr, "exception caught in ThreadPool %s\n", name_.c_str());
    fprintf(stderr, "reason: %s\n", ex.what());
    abort();
  }
  catch (...)
  {
    fprintf(stderr, "unknown exception caught in ThreadPool %s\n", name_.c_str());
    throw; // rethrow
  }
}

猜你喜欢

转载自blog.csdn.net/ZYZMZM_/article/details/99249571
今日推荐