Muduo庫——ThreadPool線程池的實現

  • 線程池ThreadPool用到ThreadMutexLockCondition

  • ThreadPool可以設置工作線程的數量,並向任務隊列放入任務。放入到任務隊列中的任務將由某個工作線程執行。

  • task使用boost::function表示,可以方便地將函數指針普通函數成員函數(結合boost::bind)lambda重載了函數調用運算符‘()’的類的對象(這些統稱爲可調用對象)放入到任務隊列當中,非常方便。

  • 需要使用條件變量來維護線程間的同步,比如:通知其他線程有任務到來了,可以向任務隊列放任務了等等。

  • muduo庫裏面的線程池是固定線程池,即創建的線程池裏面的線程個數是一定的,不是動態的。

  • 線程池裏面一般要包含線程隊列還有任務隊列,外部程序將任務存放到線程池的任務隊列中,線程池中的線程隊列執行任務,也是一種生產者和消費者模型


線程池實現總結

  1. 聲明一個任務列表(std::dequeue<boost::function<void()>> queue_;),定義一個接口給外部傳遞任務進來(任務類型是函數指針)

  2. 定義兩個線程間同步的條件變量:

Condition notEmpty;
Condition notFull;
//notEmpty 用來告知線程池裏面的線程當前是否有尚未執行的任務(即任務列表是否空)
//notFull 用來告知傳遞任務的線程當前線程池的任務列表是否已經塞滿
  1. 定義一個函數給線程池裏的線程來運

    該函數主要的作用是抓取任務列表中的任務,因爲任務是函數指針(其實是boost::function),所以當抓取到任務後直接運行抓取到的任務函數就好了,運行好之後會返回,開始下一輪的抓取,並通知其他線程自己已經空閒了。

在這裏插入圖片描述


ThreadPool.h

#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 <boost/function.hpp>
#include <boost/noncopyable.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
 
#include <deque>
 
namespace muduo
{
 
class ThreadPool : boost::noncopyable
{
 public:
  typedef boost::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
  void run(const Task& f);//運行任務,往線程池中的任務隊列添加任務
#ifdef __GXX_EXPERIMENTAL_CXX0X__
  void run(Task&& f);
#endif
 
 private:
  bool isFull() const;    //判滿
  void runInThread();   //線程池的線程運行函數
  Task take();    //獲取任務的函數
 
  mutable MutexLock mutex_;  //和條件變量配合使用的互斥鎖
  Condition notEmpty_;    //不空condition
  Condition notFull_;         //未滿condition
  string name_;//線程池的名稱
  Task threadInitCallback_;    //線程執行前的回調函數
  boost::ptr_vector<muduo::Thread> threads_;  //存放線程指針
  std::deque<Task> queue_;    //任務隊列
  size_t maxQueueSize_;     //因爲deque是通過push_back增加線程數目的,所以通過外界max_queuesize存儲最多線程數目
  bool running_;    //線程池運行標誌
};
 
}

#endif

ThreadPool.cc

#include <muduo/base/ThreadPool.h>
 
#include <muduo/base/Exception.h>
 
#include <boost/bind.hpp>
#include <assert.h>
#include <stdio.h>
 
using namespace muduo;
//構造函數的參數爲線程池的名稱 
ThreadPool::ThreadPool(const string& nameArg)
  : mutex_(),
    notEmpty_(mutex_),    //初始化的時候需要把condition和mutex關聯起來
    notFull_(mutex_),
    name_(nameArg),
    maxQueueSize_(0),   //初始化0
    running_(false)
{
}
 
ThreadPool::~ThreadPool()
{
  if (running_)    //如果線程池在運行,則停止線程池
  {
    stop();
  }            //如果沒有分配過線程,那就不存在需要釋放的內存,什麼都不做就可以了
}
 
 //啓動固定的線程池
void ThreadPool::start(int numThreads)
{
  assert(threads_.empty());   //斷言當前線程池爲空
  running_ = true;    //啓動標誌
  threads_.reserve(numThreads);   //預留reserver個空間
  for (int i = 0; i < numThreads; ++i)//創建線程
  {
    char id[32];                  //id存儲線程id
    snprintf(id, sizeof id, "%d", i+1);
    threads_.push_back(new muduo::Thread(   //boost::bind在綁定類內部成員時,第二個參數必須是類的實例
    boost::bind(&ThreadPool::runInThread, this), name_+id));//runInThread是每個線程的線程運行函數,線程爲執行任務情況下會阻塞
    threads_[i].start();    //啓動每個線程,即執行runInThread函數,但是由於線程運行函數是runInThread,所以會阻塞。
  }
  if (numThreads == 0 && threadInitCallback_) //如果線程池線程數爲0,且設置了回調函數
  {
    threadInitCallback_();  //init回調函數
  }
}
 
void ThreadPool::stop()   //線程池停止
{
  {
  MutexLockGuard lock(mutex_);   //局部加鎖
  running_ = false;
  notEmpty_.notifyAll(); //通知所有的線程
  }
  for_each(threads_.begin(),
           threads_.end(),
           boost::bind(&muduo::Thread::join, _1));   //對每個線程調用,等待所有線程退出pthread_join(),防止資源泄漏
}
 
size_t ThreadPool::queueSize() const  //thread safe
{
  MutexLockGuard lock(mutex_);
  return queue_.size();
}
 
//添加任務,所以說線程池這個線程池執行任務是靠任務隊列,客戶端需要執行一個任務,必須首先將該任務push進任務隊列,等侯空閒線程處理
void ThreadPool::run(const Task& task)    
{
  if (threads_.empty())     //如果線程池爲空,說明線程池未分配線程
  { 
    task();    //由當前線程執行任務
  }
  else
  {
    MutexLockGuard lock(mutex_);
    while (isFull())    //當任務隊列滿的時候,循環
    {
      notFull_.wait();    //一直等待任務隊列不滿   //這個鎖在take()取任務函數中,取出任務隊列未滿,喚醒該鎖
    }
    assert(!isFull()); 
 
    queue_.push_back(task);   //當任務隊列不滿,就把該任務加入線程池的任務隊列
    notEmpty_.notify();  //當添加了某個任務之後,任務隊列肯定不是空的,通知某個等待從queue_中取task的線程取完任務後runInThread會執行任務
  }
}
 
#ifdef __GXX_EXPERIMENTAL_CXX0X__
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();
  }
}
#endif
 
//take函數是每個線程都執行的,需要考慮線程安全,考慮多線程下取任務的線程安全性,只能串行化
ThreadPool::Task ThreadPool::take()     //獲取任務的函數
{
  MutexLockGuard lock(mutex_);   //注意,必須用鎖
  // always use a while-loop, due to spurious wakeup     //防止驚羣效應。
  while (queue_.empty() && running_)     //如果任務隊列爲空,並且線程池處於運行態
  {			//隊列爲空時沒有使用線程池
    notEmpty_.wait();  //等待。條件變量需要用while循環,防止驚羣效應。
  } 				   //因爲所有的線程都在等同一condition,即notempty,只能有線程在wait返回時拿到mutex,並消耗資源
					  //其他線程雖然被notify同樣返回,但資源已被消耗,queue爲空(以1個任務爲例),其他線程就在while中繼續等待
  Task task;//定義任務變量,Task是一個函數類型
  if (!queue_.empty())    //有任務到來
  {
    task = queue_.front();//從隊首取出任務
    queue_.pop_front();//刪除該任務
    if (maxQueueSize_ > 0)    //如果未設置會等於0,不需要喚醒notFull
    {
      notFull_.notify();    //取出一個任務之後,如任務隊列長度大於0,喚醒notfull未滿鎖
    }
  }
  return task;//返回該任務
}
 
bool ThreadPool::isFull() const   //not thread safe 
{
  mutex_.assertLocked(); //調用確保被使用線程鎖住,因爲isFull函數不是一個線程安全的函數,外部調用要加鎖
  return maxQueueSize_ > 0 && queue_.size() >= maxQueueSize_;   //因爲deque是通過push_back增加線程數目的,所以通過外界max_queuesize存儲最多線程數目
}
 
//線程運行函數,無任務時都會阻塞在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
  }
}

最後任務什麼時候截止呢?線程池何時才能執行 stop()

線程池的結束不是庫的設計者能夠決定呢,這是開發者(用戶)決定的,讓目前進行的線程池停止,必須保證當前所有的任務已經全部完成,從這個角度出發,我們可以將最後一個任務設計成一個結束的任務標誌,怎麼設計呢?

這就可以採用CountDownLatch,CountDownLatch 是一個倒計時類

  • 它的用途有:
    (1)主線程發起多個子線程,等這些子線程各自都完成一定的任務之後,主線程才繼續執行。通常用於主線程等待多個子線程完成初始化。
    (2)主線程發起多個子線程,子線程都等待主線程,主線程完成其他一些任務之後通知所有子線程開始執行。通常用於多個子線程等待主線程發出“起跑”命令。
//設置結束標誌
  muduo::CountDownLatch latch(1);
  pool.run(boost::bind(&muduo::CountDownLatch::countDown, &latch));
  latch.wait();
  pool.stop();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章