muduo庫學習篇-threadPool類的封裝

在多線程編程中一般情況下,我們都會使用到線程池,因此muduo庫也有對線程池的封裝,接下來我們看看muduo庫對線程池的實現,基本上和大部分的線程池的實現,大同小異。

ThreadPool.h
// 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_THREADPOOL_H
#define MUDUO_BASE_THREADPOOL_H

#include "muduo/base/Condition.h"
#include "muduo/base/Mutex.h"
#include "muduo/base/Thread.h"
#include "muduo/base/Types.h"

#include <deque>
#include <vector>

namespace muduo
{

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;

  // Could block if maxQueueSize > 0
  // There is no move-only version of std::function in C++ as of C++14.
  // So we don't need to overload a const& and an && versions
  // as we do in (Bounded)BlockingQueue.
  // https://stackoverflow.com/a/25408989
  void run(Task f);

 private:
  bool isFull() const REQUIRES(mutex_);
  void runInThread();
  Task take();

  mutable MutexLock mutex_;
  Condition notEmpty_ GUARDED_BY(mutex_);
  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_;
};

}  // namespace muduo

#endif  // MUDUO_BASE_THREADPOOL_H

muduo庫線程池的成員變量有:

  • 互斥鎖mutex,用於線程間數據同步使用
  • 兩個條件變量,notEmpty_,notFull_用來判斷任務池中任務的數量
  • 線程名稱
  • 線程回調函數,用來消費任務
  • 線程指針數組
  • 任務deque隊列
  • 任務隊列的中允許存放的最多任務個數
  • 線程是否啓動

muduo ThreadPool框架圖:
在這裏插入圖片描述

ThreadPool.cc

// 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)

#include "muduo/base/ThreadPool.h"

#include "muduo/base/Exception.h"

#include <assert.h>
#include <stdio.h>

using namespace muduo;

ThreadPool::ThreadPool(const string& nameArg)
  : mutex_(),
    notEmpty_(mutex_),
    notFull_(mutex_),
    name_(nameArg),
    maxQueueSize_(0),
    running_(false)
{
}

ThreadPool::~ThreadPool()
{
  if (running_)
  {
    stop();
  }
}

void ThreadPool::start(int numThreads)
{
  assert(threads_.empty());
  running_ = true;
  threads_.reserve(numThreads);
  for (int i = 0; i < numThreads; ++i)
  {
    char id[32];
    snprintf(id, sizeof id, "%d", i+1);
    threads_.emplace_back(new muduo::Thread(
          std::bind(&ThreadPool::runInThread, this), name_+id));
    threads_[i]->start();
  }
  if (numThreads == 0 && threadInitCallback_)
  {
    threadInitCallback_();
  }
}

void ThreadPool::stop()
{
  {
  MutexLockGuard lock(mutex_);
  running_ = false;
  notEmpty_.notifyAll();
  }
  for (auto& thr : threads_)
  {
    thr->join();
  }
}

size_t ThreadPool::queueSize() const
{
  MutexLockGuard lock(mutex_);
  return queue_.size();
}

void ThreadPool::run(Task task)
{
  if (threads_.empty())
  {
    task();
  }
  else
  {
    MutexLockGuard lock(mutex_);
    while (isFull())
    {
      notFull_.wait();
    }
    assert(!isFull());

    queue_.push_back(std::move(task));
    notEmpty_.notify();
  }
}

ThreadPool::Task ThreadPool::take()
{
  MutexLockGuard lock(mutex_);
  // always use a while-loop, due to spurious wakeup
  while (queue_.empty() && running_)
  {
    notEmpty_.wait();
  }
  Task task;
  if (!queue_.empty())
  {
    task = queue_.front();
    queue_.pop_front();
    if (maxQueueSize_ > 0)
    {
      notFull_.notify();
    }
  }
  return task;
}

bool ThreadPool::isFull() const
{
  mutex_.assertLocked();
  return maxQueueSize_ > 0 && queue_.size() >= maxQueueSize_;
}

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


muduo庫線程池的實現:

  • 構造函數對成員變量進行初始化,互斥鎖條件變量的構造,任務隊列的默認任務數量爲0;
  • 析構函數對線程池的狀態進行判斷,如果線程池不是關閉狀態關閉線程
  • start函數設置線程的數量,並設置線程的狀態爲running,並且通過 threads_.reserve(numThreads)設置線程池中線程的數量,同時設置線程的回調函數
  • stop函數,將線程池的狀態修改爲停止,同時等待所有的線程結束,看實現代碼,沒有對任務隊列中爲處理任務數量進行判斷,如果停止,應該保證任務隊列中的所有任務都處理結束,而muduo庫的代碼,只是將每個線程手中執行的任務處理完成,因此個人認爲stop函數的實現不是很完美
  • run函數將外部的任務放入任務隊列,同時在放入任務隊列時判斷任務隊列是否已滿,如果已滿就阻塞等待,知道任務隊列有空餘的空間
  • take函數,線程從線程池中取任務如果任務隊列任務數量爲空,那麼就阻塞等待,知道有任務進入任務隊列
  • runInThread函數,線程的工作loop
由於muduo庫是基於對象編程的設計理念,所以muduo庫的任務隊列中存放的都是回調函數,線程池的另外一種實現方式就是將muduo庫中任務隊列換成一個taskbase的基類指針,通過虛函數多態來實現對任務的動態調用,但是如果是這樣設計的話,muduo庫就必須提供一個taskbase的基類,外部的使用者就必須繼承該基類才能夠使用muduo庫。各有各的好處吧,如果我們是在一個項目中設計一個ThreadPool的話,個人認爲通過基類指針這種方式設計任務隊列更好些,如果是封裝一個框架類型的話還是muduo庫的這種更加合理吧,使外部threadpool的使用者之關係自己的業務設計,而無需與threadpool做匹配。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章