boost之ThreadPool

Mr_John_Liang發表於2013-05-30

boost之ThreadPool

分類: 技術資料 2011-04-02 17:59 549人閱讀 評論(1) 收藏 舉報

threadpool是基於boost庫實現的一個執行緒池子庫,但執行緒池實現起來不是很複雜。我們從threadpool中又能學到什麼東西呢?

它是基於boost庫實現的,如果大家對boost庫有興趣,看看一個簡單的實現還是可以學到點東西的。

threadpool基本功能

1、任務封裝,包括普通任務(task_func)和優先順序任務(prio_task_func)。

2、排程策略,包括fifo_scheduler(先進先出)、lifo_scheduler(後進先出)、prio_scheduler(優先順序)。

3、結束策略,包括wait_for_all_tasks(全部任務等待)、wait_for_active_tasks(啟用任務等待)、immediately(立即結束)。

4、動態修改執行緒池個數功能。

5、基於future封裝的非同步返回值獲取功能。

 

 在sorceforge上有一個用boost編寫的執行緒池。該執行緒池和boost結合的比較好,並且提供了多種任務執行策略,使用也非常簡單。 下載地址: http://threadpool.sourceforge.net/ 這個執行緒池不需要編譯,只要在專案中包含其標頭檔案就可以了。

一、原始碼分析

quickstart分析(/threadpool/libs/threadpool/quickstart)

這個例子的程式碼很簡單,但已經全部展示了執行緒池的核心內容,包括建立、排程、同步等操作。

 

view plaincopy to clipboardprint?

// Create fifo thread pool container with two threads.  

pool tp(2);  

     

// Add some tasks to the pool.  

tp.schedule(&first_task);  

tp.schedule(&second_task);     

    

// Wait until all tasks are finished.  

tp.wait(); 

// Create fifo thread pool container with two threads.

pool tp(2);

  

// Add some tasks to the pool.

tp.schedule(&first_task);

tp.schedule(&second_task);  

 

// Wait until all tasks are finished.

tp.wait();

 

pool的定義具體見pool.hpp,但使用了pimpl模式,核心程式碼見pool_core.hpp檔案。

下面是pool的定義

typedef thread_pool<task_func, fifo_scheduler, static_size, resize_controller, wait_for_all_tasks> fifo_pool;

typedef fifo_pool pool;

從上面可以知道,pool實際就是fifo_pool,從模板引數可以看到,使用了fifo_scheduler和wait_for_all_tasks。

 

對於執行緒池有點理解的都知道,一般都是那幾樣東西,執行緒的封裝,條件變數,佇列資料結構。

所以簡單的能做的很簡單,複雜的的就看你的策略需求了。

對基於boost庫的threadpool子庫來說,上面的三樣東西都是現成的,執行緒封裝和條件變數直接使用thread子庫就行,佇列使用stl的標準容器。

 

task_adaptors.hpp

對執行緒任務的封裝,所謂task,我們可以理解成需要執行的函式。

threadpool最大限度的使用了function和bind功能來封裝函式,這點和thread子庫類似。

檔案中涉及的內容主要有三個:task_func、prio_task_func和looped_task_func。

 

對普通task的封裝

typedef function0<void> task_func;

如果對bind和function熟悉的應該很好理解。

 

對優先順序任務的封裝

class prio_task_func

這個類很簡單,過載了兩個方法,

operator()是仿函式的用法,

operator<是用於優先順序比較使用的,用於stl容器的元素比較。

 

 

size_policies.hpp

對size的封裝,包括empty_controller、resize_controller和static_size。

 

 

shutdown_policies.hpp

對執行緒池結束的策略封裝,包括wait_for_all_tasks、wait_for_active_tasks和immediately。

這幾個類很簡單,具體操作封裝在pool中。

執行緒池執行過程中,包括佇列中等待的task,執行緒正在執行的task。

所以結束的時候,對這些task的策略操作是有選擇的。

 

 

scheduling_policies.hpp

對任務排程測試的封裝,包括fifo_scheduler、lifo_scheduler和prio_scheduler。

實際上,這三個類的相似程度很高,大家可能更喜歡用繼承和虛擬函式實現。

前面說到儲存task的佇列資料結構,在這裡就看的很清楚了。

fifo和lifo使用的是std::deque,prio使用的是std::priority_queue,其他部分程式碼沒什麼好說的了。

 

 

pool_adaptors.hpp

對全域性schedule函式的幾種封裝。

 

 

future.hpp

好像thread子庫也有future,但不清楚是否是一樣的內容。

threadpool的future是為了封裝非同步函式呼叫返回值實現的。

簡單點理解,就是schedule任務的時候,把一個指標在兩者間繫結起來,後面就可以通過future來獲取返回值了。

當然,獲取返回值的過程應該是阻塞的,任務未完成時只能wait。

 

 

locking_ptr.hpp

LockingPtr的簡單封裝,具體可google《volatile - Multithreaded Programmer's Best Friend》。

threadpool大量使用了volatile關鍵字,所以需要LockingPtr保護。

 

 

scope_guard.hpp

對函式物件的封裝,利用C++解構函式時呼叫一個在建構函式時繫結的函式物件。

 

 

worker_thread.hpp

對工作執行緒的封裝,這個封裝不是指底層執行緒api封裝,因為這部分是由boost的thread子庫提供的。

封裝針對的是迴圈執行task的邏輯函式(執行緒跑起來就loop run某個函式,從佇列中獲取task執行,空閒時等待。)

我們重點看的是run和create_and_attach。

這兩個函式連起來看,就很清楚了,create_and_attach通過bind方式生成一個thread執行run方法。

run方法中的這條語句就是一個簡單的loop操作,

while(m_pool->execute_task()) {}

所以,當execute_task返回值為false時,run函式就結束了,bind該函式的thread也就結束了。

 

 

ok,來到這裡,有必要簡單的把整個呼叫過程說明一下。

// Create fifo thread pool container with two threads.

pool tp(2);

 

該操作會呼叫pool的建構函式

 

view plaincopy to clipboardprint?

thread_pool(size_t initial_threads = 0)  

: m_core(new pool_core_type)  

, m_shutdown_controller(static_cast<void*>(0), bind(&pool_core_type::shutdown, m_core))  

{  

  size_policy_type::init(*m_core, initial_threads);  

thread_pool(size_t initial_threads = 0)

: m_core(new pool_core_type)

, m_shutdown_controller(static_cast<void*>(0), bind(&pool_core_type::shutdown, m_core))

{

  size_policy_type::init(*m_core, initial_threads);

}

 

由於pimpl模式,所以所有程式碼都封裝在m_core內實現的。

pool預設的執行緒個數為0,通過size_policy_type::init來初始化。

而size_policy_type是一個模板引數,pool對應的是fifo,所以也就是static_size型別了。

 

//static_size類的init函式

 

view plaincopy to clipboardprint?

static void init(Pool& pool, size_t const worker_count)  

{  

  pool.resize(worker_count);  

static void init(Pool& pool, size_t const worker_count)

{

  pool.resize(worker_count);

}

 

//pool_core的resize函式

這個函式有點長,主要是做動態配置執行緒個數的邏輯操作,create_and_attach也是在這裡呼叫的。

 

view plaincopy to clipboardprint?

//worker_thread的create_and_attach函式  

static void create_and_attach(shared_ptr<pool_type> const & pool)  

{  

 shared_ptr<worker_thread> worker(new worker_thread(pool));  

 if(worker)  

 {  

   //run是執行緒的loop函式  

   worker->m_thread.reset(new boost::thread(bind(&worker_thread::run, worker)));  

 }  

//worker_thread的create_and_attach函式

static void create_and_attach(shared_ptr<pool_type> const & pool)

{

 shared_ptr<worker_thread> worker(new worker_thread(pool));

 if(worker)

 {

   //run是執行緒的loop函式

   worker->m_thread.reset(new boost::thread(bind(&worker_thread::run, worker)));

 }

}

 

view plaincopy to clipboardprint?

//worker_thread的run函式  

void run()  

{   

  scope_guard notify_exception(bind(&worker_thread::died_unexpectedly, this));  

  while(m_pool->execute_task()) {} //loop直到返回值為false  

  notify_exception.disable();  

  m_pool->worker_destructed(this->shared_from_this());  

//worker_thread的run函式

void run()

{

  scope_guard notify_exception(bind(&worker_thread::died_unexpectedly, this));

  while(m_pool->execute_task()) {} //loop直到返回值為false

  notify_exception.disable();

  m_pool->worker_destructed(this->shared_from_this());

}

 

 

//pool_core的execute_task函式

這個函式有點長,簡單點說,就是從佇列中獲取task然後執行,如果佇列為空,則執行緒需要wait操作。

由於threadpool支援動態resize執行緒個數,從該函式我們也是可以看出來是如何做到的。

 

view plaincopy to clipboardprint?

// decrease number of threads if necessary  

if(m_worker_count > m_target_worker_count)  

{   

  return false; // terminate worker  

// decrease number of threads if necessary

if(m_worker_count > m_target_worker_count)

{

  return false; // terminate worker

}

 

 

pool內部使用了多個整數來記錄現在個數,譬如m_worker_count和m_target_worker_count。

m_worker_count是當前啟用執行中的執行緒個數。

m_target_worker_count是最新動態配置的執行緒個數。

當個數不匹配時,通過返回false方式結束執行緒。

 

 

// Add some tasks to the pool.

tp.schedule(&first_task);

  

 

view plaincopy to clipboardprint?

//thread_pool的schedule函式  

bool schedule(task_type const & task)  

{   

  return m_core->schedule(task);  

}  

 

//pool_core的schedule函式(和execute_task函式強相關)  

bool schedule(task_type const & task) volatile 

{   

  locking_ptr<pool_type, recursive_mutex> lockedThis(*this, m_monitor);   

    

  if(lockedThis->m_scheduler.push(task))  

  {  

 //task成功入佇列後,notify_one一個執行緒。  

 lockedThis->m_task_or_terminate_workers_event.notify_one();  

 return true;  

  }  

  else 

  {  

 return false;  

  }  

//thread_pool的schedule函式

bool schedule(task_type const & task)

{

  return m_core->schedule(task);

}

 

//pool_core的schedule函式(和execute_task函式強相關)

bool schedule(task_type const & task) volatile

{

  locking_ptr<pool_type, recursive_mutex> lockedThis(*this, m_monitor);

 

  if(lockedThis->m_scheduler.push(task))

  {

 //task成功入佇列後,notify_one一個執行緒。

 lockedThis->m_task_or_terminate_workers_event.notify_one();

 return true;

  }

  else

  {

 return false;

  }

}

 

 

 

 

// Wait until all tasks are finished.

tp.wait();

 

 

//pool_core的wait函式

void wait(size_t const task_threshold = 0) const volatile

bool wait(xtime const & timestamp, size_t const task_threshold = 0) const volatile

wait函式是一個阻塞操作,內部邏輯實現使用了一個條件變數,提供超時等待方式。

 

 

二、boost執行緒池使用例項

執行緒池可以減少建立和切換執行緒的額外開銷,利用已經存在的執行緒多次迴圈執行多個任務從而提高系統的處理能力,有關執行緒池的概念可google搜尋,下面將其使用例項:

#include <iostream>
#include <sstream>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>

#include <boost/threadpool.hpp>

using namespace std;
using namespace boost::threadpool;


//
// Helpers
boost::mutex m_io_monitor;

void print(string text)
{
 boost::mutex::scoped_lock lock(m_io_monitor);//每個執行緒使用全域性互斥來保證每次只有一個執行緒執行
 cout << text;
}

template<typename T>
string to_string(T const & value)
{
 ostringstream ost;
 ost << value;
 ost.flush();
 return ost.str();
}

 

//
// An example task functions
void task_1()
{
 print("  task_1()/n");
}

void task_2()
{
 print("  task_2()/n");
}

void task_3()
{
 print("  task_3()/n");
}

int task_4()
{
 print("  task_4()/n");
 return 4;
}

void task_with_parameter(int value)
{
 print("  task_with_parameter(" + to_string(value) + ")/n");
}

int loops = 0;
bool looped_task()
{
 print("  looped_task()/n");
 return ++loops < 5; 
}


int task_int()
{
 print("  task_int()/n");
 return 23;
}


void fifo_pool_test()
{
 pool tp;

 tp.schedule(&task_1);
 tp.schedule(boost::bind(task_with_parameter, 4));

 if(!tp.empty())
 {
  tp.clear();  // remove all tasks -> no output in this test
 }

 size_t active_threads   = tp.active();
 size_t pending_threads  = tp.pending();
 size_t total_threads    = tp.size();

 size_t dummy = active_threads + pending_threads + total_threads;
 dummy++;

 tp.size_controller().resize(5);
 tp.wait();
}

void lifo_pool_test()
{
 lifo_pool tp;
 tp.size_controller().resize(0);
 schedule(tp, &task_1);
 tp.size_controller().resize(10);
 tp.wait();
}

void prio_pool_test()
{
 prio_pool tp(2);
 schedule(tp, prio_task_func(1, &task_1));
 schedule(tp, prio_task_func(10,&task_2));
}


void future_test()
{
 fifo_pool tp(5);
 future<int> fut = schedule(tp, &task_4);
 int res = fut();
}


int main (int , char * const []) 
{
 fifo_pool_test();
 lifo_pool_test();
 prio_pool_test();
 future_test();
 return 0;
}

任務返回值的獲取:

一般非同步呼叫中,返回值的獲取有同步獲取和非同步獲取兩種形式。

同步獲取返回值:

int task_int_23()
{
    cout<<"task_int_23()/n";
    return 23;
}

future<int> res = schedule(tp, &task_int_23);
res.wait();

cout<<"get res value:"<<res.get()<<endl;

非同步獲取返回值:

不知道是設計者就不打算使用非同步回撥獲取返回值還是我看的不夠仔細,非同步獲取返回值的方式還真沒有找著,只好自己簡單的寫了一個回撥的仿函式來實現非同步返回值的獲取。

//R為任務函式的返回值型別
template<class R>
class callback_task
{
    typedef boost::function<void (R)> callback;
    typedef boost::function<R ()> function;

private:
    callback c_;
    function f_;

public:
    //F: 任務執行函式 C:結果回撥函式
    template<class F,class C>
    callback_task(F f,C c)
    {
        f_ = f;
        c_ = c;
    }

    void operator()()
    {
        c_(f_());
    }
};

通過這個物件可以很容易的實現非同步結果的回撥。

//task_int_23的結果回撥函式 
void callback(int k)
{
    cout<<"get callback value:"<<k<<endl;
}

//通過回撥的形式獲取任務的返回值 
tp.schedule(callback_task<int>(&task_int_23,&callback));

 

參考資料:

boost官方網站:  http://www.boost.org/

上一篇:Pro *C/C++學習筆記下一篇:通過自定義協議在BS裡面啟動CS程式

相關文章