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線程池使用智能指針來管理線程對象的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章