-
線程池ThreadPool用到
Thread
、MutexLock
、Condition
。 -
ThreadPool可以設置工作線程的數量,並向任務隊列放入任務。放入到任務隊列中的任務將由某個工作線程執行。
-
task使用boost::function表示,可以方便地將
函數指針
、普通函數
、成員函數(結合boost::bind)
、lambda
、重載了函數調用運算符‘()’的類的對
象(這些統稱爲可調用對象)放入到任務隊列當中,非常方便。 -
需要使用條件變量來維護線程間的同步
,比如:通知其他線程有任務到來了,可以向任務隊列放任務了等等。 -
muduo庫裏面的線程池是固定線程池,即創建的線程池裏面的線程個數是一定的,不是動態的。
-
線程池裏面一般要包含
線程隊列
還有任務隊列
,外部程序將任務存放到線程池的任務隊列中,線程池中的線程隊列執行任務,也是一種生產者和消費者模型
。
線程池實現總結
-
聲明一個任務列表(std::dequeue<boost::function<void()>> queue_;),定義一個接口給外部傳遞任務進來(任務類型是函數指針)
-
定義兩個線程間同步的條件變量:
Condition notEmpty;
Condition notFull;
//notEmpty 用來告知線程池裏面的線程當前是否有尚未執行的任務(即任務列表是否空)
//notFull 用來告知傳遞任務的線程當前線程池的任務列表是否已經塞滿
-
定義一個函數給線程池裏的線程來運
該函數主要的作用是抓取任務列表中的任務,因爲任務是函數指針(其實是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();