1. 程式人生 > >muduo庫中對執行緒池的實現(2)

muduo庫中對執行緒池的實現(2)

這兩天花時間嘗試實現了一下執行緒池,本來是想完全自己寫的,但是寫著寫著就去參考muduo庫的執行緒池了,實現思路和muduo庫的執行緒池一模一樣。我嘗試著在不考慮執行緒安全的情況下對muduo庫執行緒池的實現做一下簡述。

1. 核心思想

執行緒池的關鍵點在於兩點:空閒執行緒怎麼知道任務已經傳遞進來了,執行緒間競爭任務 如果我在沒有接觸muduo庫之前,我的想法肯定很簡單,直接一個pthread_mutex上手。 然而muduo中使用了條件變數來實現執行緒池。如果有學過一點程序訊號的話,會發現程序訊號和條件變數是很像的東西,不同的是,條件變數喚醒的是阻塞住的條件變數。 使用條件變數能很好的避免執行緒對臨界資源的競爭(其實在我看來,條件變數更多得像是一種對底層的封裝,因為了解了條件變數的特性之後會發現,如果沒有條件變數的話,我自己也會用土方法實現一個類似條件變數的功能)。

2. 兩個條件變數

muduo庫中使用了兩個條件變數:notEmpty, notFull notEmpty用於通知執行緒池中的執行緒不要再阻塞了,試試看從任務列表中獲取一個任務。 notFull用於通知給任務的執行緒:當前任務列表已經沒有被塞滿了,現在可以放置新的任務到任務列表中(muduo的執行緒池可以設定任務列表最大值,雖然內部使用的queue來管理任務列表) 條件變數的實際應用場景:

放置任務:

void ThreadPool::run(const Task& task)              // 其他執行緒給執行緒池塞任務的介面
{
  if (threads_.empty())   // 如果執行緒列表為空,那就只能自己執行了
  {
    task();               // 執行回撥函式(函式指標)
  }
  else
  {
    MutexLockGuard lock(mutex_);        // 鎖住臨界區
    while (isFull())                    // 判斷當前任務列表是否已經滿了
    {
      notFull_.wait();                  // 如果滿了,那就等待任務執行緒把任務取走後通知當前執行緒可以放置任務了
    }
    assert(!isFull());

    queue_.push_back(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;           // 將獲取到的任務返回給任務執行緒
}

3. 關閉執行緒池

當執行緒池物件要被析構,或者使用者想關閉執行緒池的時候,肯定不能直接析構執行緒物件,因為當前執行緒池中的執行緒物件還在工作,這樣肯定不安全。這裡簡單講一下陳碩的解決方案。 muduo執行緒池類中定義了一個bool型別的成員變數:running_,該變數用來告訴執行緒池中的執行緒物件現線上程池的狀態(而非執行緒的狀態,這裡說的執行緒池狀態是指邏輯狀態,真實狀態依賴具體的執行緒物件),當running_變為假的時候,就意味著執行緒物件應該終止迴圈,退出執行緒了。 執行緒池關閉的時候會通知所有執行緒物件,終止所有執行緒的等待狀態,執行緒物件終止等待通知的狀態後會嘗試獲取任務列表以及判斷當前執行緒池的邏輯狀態(running_),如果running_為假,那麼執行緒物件就會從獲取任務的迴圈中跳出,繼而退出執行緒。 我們看一下muduo執行緒池的解構函式:
void ThreadPool::stop()
{
  {
  MutexLockGuard lock(mutex_);
  running_ = false;                                // 改變執行緒池邏輯狀態
  notEmpty_.notifyAll();                           // 通知所有執行緒物件來看一眼running_的狀態
  }
  for_each(threads_.begin(),
           threads_.end(),
           boost::bind(&muduo::Thread::join, _1)); // 挨個等待執行緒物件退出執行緒
}

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

另外說明一點,muduo執行緒池使用智慧指標來管理執行緒物件的。