muduo網路庫學習筆記(5):執行緒池的實現

li27z發表於2016-08-14

瞭解生產者-消費者問題

生產者-消費者問題也被稱為有界緩衝區問題,兩個程式/執行緒共享一個公共的固定大小的緩衝區。其中一個是生產者,將資訊放入緩衝區;另一個是消費者,從緩衝區中取出資訊。

問題在於當緩衝區已滿,而此時生產者還想向其中放入一個新的資料項的情況。其解決方法就是讓生產者休眠,待消費者從緩衝區中取出一個或多個資料項時再喚醒它。同樣地,當消費者試圖從緩衝區中取資料而發現緩衝區為空時,消費者就休眠,直到生產者向其中放入一些資料時再將其喚醒。

// 以下程式碼只是對生產者-消費者問題的大致描述
// 因為對count的訪問未加限制,可能會出現競爭條件問題

#define N 100   // 緩衝區的槽數目
int count = 0;  // 緩衝區中的資料項的數目

// 生產者
void producer(void)
{
    int item;

    while(TRUE){
        item = produce_item();
        if(count == N) sleep();
        insert_item(item);
        count = count + 1;
        if(count == 1) wakeup(consumer);
    }

}

// 消費者
void consumer(void)
{
    int item;

    while(TRUE){
        if(count == 0) sleep();
        item = remove_item();
        count = count - 1;
        if(count == N - 1) wakeup(producer);
        consume_item(item);
    }
}

執行緒池問題本質上也是一個生產者-消費者問題

外部執行緒可以向執行緒池中的任務佇列新增任務,相當於“生產者”;一旦任務佇列中有任務,就喚醒執行緒佇列中的執行緒來執行這些任務,這些執行緒就相當於“消費者”。模型如下圖。

這裡寫圖片描述

muduo ThreadPool類圖:
這裡寫圖片描述

(1)任務佇列的實現用到了STL的deque容器
deque容器為一個給定型別的元素進行線性處理,像向量一樣,它能夠快速地隨機訪問任一個元素,並且能夠高效地插入和刪除容器的尾部元素。但它又與vector不同,deque支援高效插入和刪除容器的頭部元素,因此也叫做雙端佇列。

deque常用函式如下:

#include <deque>

void push_front(const T& x);  // 雙端佇列頭部增加一個元素x

void push_back(const T& x);   // 雙端佇列尾部增加一個元素x

void pop_front();             // 刪除雙端佇列中最前一個元素

void pop_back();              // 刪除雙端佇列中最後一個元素

void clear();                 // 清空雙端佇列中最後一個元素

reference at(int pos);        // 返回pos位置元素的引用

reference front();            // 返回手元素的引用

reference back();             // 返回尾元素的引用

bool empty() const;           // 向量是否為空,若為true,則向量中無元素

(2)幾個成員函式的說明

檔名:ThreadPool.cc

// 啟動執行緒池,啟動的執行緒是固定個數的(numThreads)
void ThreadPool::start(int numThreads)
{
  assert(threads_.empty()); // 斷言執行緒池是空的
  running_ = true; // 執行狀態標記置為true
  threads_.reserve(numThreads); // 為執行緒池預留指定大小的空間
  // 建立執行緒
  for (int i = 0; i < numThreads; ++i)
  {
    char id[32];
    snprintf(id, sizeof id, "%d", i);
    threads_.push_back(new muduo::Thread(
          boost::bind(&ThreadPool::runInThread, this), name_+id));
    threads_[i].start();
  }
}
檔名:ThreadPool.cc

// 關閉執行緒池
void ThreadPool::stop()
{
  {
  MutexLockGuard lock(mutex_);
  running_ = false; // 執行狀態標識置為false
  cond_.notifyAll(); // 通知所有執行緒
  }
  // 等待所有執行緒關閉
  // boost::bind呼叫類成員函式時需要傳入類成員函式指標、類物件指標...
  for_each(threads_.begin(),
           threads_.end(),
           boost::bind(&muduo::Thread::join, _1));
}
檔名:ThreadPool.cc

// 執行任務  
void ThreadPool::run(const Task& task)  
{  
  // 如果執行緒池沒有執行緒,那麼直接執行任務
  // 也就是說假設沒有消費者,那麼生產者直接消費產品,而不把任務加入任務佇列
  if (threads_.empty())  
  {  
    task();  
  }  
  // 如果執行緒池有執行緒,則將任務新增到任務佇列  
  else  
  {  
    MutexLockGuard lock(mutex_);   
    queue_.push_back(task);  
    cond_.notify();  
  }  
}  
檔名:ThreadPool.cc

// 任務分配函式(獲取任務)
// 執行緒池函式或者執行緒池裡面的函式都可以到這裡取出一個任務
// 然後在自己的執行緒中執行任務,返回一個任務指標  
ThreadPool::Task ThreadPool::take()  
{  
  MutexLockGuard lock(mutex_);  
  // always use a while-loop, due to spurious wakeup(虛假喚醒)
  // 任務佇列為空且執行緒池處於執行狀態,需要等待任務的到來
  while (queue_.empty() && running_)  
  {  
    cond_.wait();  
  }  
  Task task;  
  if(!queue_.empty())  
  {  
    // 獲取任務並彈出
    task = queue_.front();  
    queue_.pop_front();  
  }  
  return task;  
} 

相關文章