2015年4月10日 星期五

為 C++11 增加 Semaphore 功能

C++11 沒提供 Semaphore 只好做一個,其中主要的關鍵是 std::Condition_variable  的應用
#include <mutex>
#include <iostream>
#include <condition_variable>
// 適用於 C++11 的 Semaphore
// 因 C++11 沒提供
class Semaphore
{
  unsigned int m_ThreadMaxNum; // 最多可多少 thread 允許通行
  std::mutex m_Semaphore_mutex;
  std::condition_variable m_condition;
public:
  // Constructor
  // ThreadMaxNum = 最多可多少 thread 允許通行
  Semaphore(unsigned int ThreadMaxNum)
  {
    m_ThreadMaxNum = ThreadMaxNum;
  }
  // Destructor
  ~Semaphore()
  {
  }
  // 進入關鍵區域
  void Enter()
  {
    std::unique_lock<std::mutex> lock{ m_Semaphore_mutex };
    m_condition.wait(lock, 
      [&]()->bool{return m_ThreadMaxNum > 0;}
    );
    --m_ThreadMaxNum;
  }
  // 離開關鍵區域
  void Leave()
  {
    std::lock_guard<std::mutex> lock{ m_Semaphore_mutex };
    ++m_ThreadMaxNum;
    m_condition.notify_one();
  }
  // 輔助類別
  // 幫助自動叫用 Semaphore::Enter() 和 Semaphore::Leave()
  // 對於回傳值的函數必須使用
  class SemaphoreHandle
  {
    Semaphore &S;
  public:
    SemaphoreHandle(const Semaphore &Semaphore_Arg)
      :S(const_cast<Semaphore &> (Semaphore_Arg))
    {
      S.Enter();
    }
    ~SemaphoreHandle()
    {
      S.Leave();
    }
  };
};
// 使用範例
class A
{
  Semaphore m_Semaphore;
public:
  A()
    :m_Semaphore(5) // 最多只能有 5 個 thread 同時使用
  {}
  ~A(){}
  void Fn1()
  {
    m_Semaphore.Enter();
    std::cout << "Fn1" << std::endl;
    m_Semaphore.Leave();
  }
  void Fn2()
  {
    Semaphore::SemaphoreHandle AutoSH(m_Semaphore);
    std::cout << "Fn2" << std::endl;
  }
  int Fn3()
  {
    // 對於有回傳值的函數必須使用,以確保叫用 return 後
    // 才呼叫 Semaphore::Leave()
    Semaphore::SemaphoreHandle AutoSH(m_Semaphore);
    std::cout << "Fn3" << std::endl;
    return 0;
  }
};

了解一下 Semaphore::Enter() 到底做了什麼事
void Enter()
{
  // 搶奪 m_Semaphore_mutex 以鎖住取得繼續執行的權力
  std::unique_lock<std::mutex> lock{ m_Semaphore_mutex };

  // 會執行至此表示已鎖住 m_Semaphore_mutex
  m_condition.wait(lock,
      // 在 m_Semaphore_mutex 鎖住的情形下,
      //    檢查  m_ThreadMaxNum > 0,
      //    若為 true,繼續往下執行
      //    若為 false,會將目前線程的 lock 保存起來,並把
      //    目前線程 block,把 m_Semaphore_mutex 解鎖
      // 當 m_condition 獲得 notify 時,會任選一個保
      // 存的線程解除 block,讓該線程的 lock 加入
      // m_Semaphore_mutex 的搶奪,再重覆以上縮排的處理
      [&]()->bool{return m_ThreadMaxNum > 0;}    );

  // 在 m_Semaphore_mutex 鎖住的情形下,改變 m_ThreadMaxNum
  // 的值
  --m_ThreadMaxNum;
} // 離開區塊,會自動引發 lock 的解構函數,將 m_Semaphore_mutex 解鎖

注意,發出 notify 的線程須先取得鎖住 m_Semaphore_mutex 的權
力,Semaphore::Leave() 已做到

再用例子來了解它的意義,直接使用 Semaphore 的樣貌如下
Semaphore Semaphore(10);
void F1()
{
  Semaphore.Enter();
  while (true);
  Semaphore.Leave();
}
這樣最多只能有 10 個線程在跑那跑不出來的 loop。
若把 Semaphore 展開來用,樣貌如下
unsigned int ThreadMaxNum = 10; // 最多可多少 thread 允許通行
std::mutex Semaphore_mutex;
std::condition_variable condition;
void F2()
{  
  std::unique_lock<std::mutex> lock1{ Semaphore_mutex };
  condition.wait(lock1,
    [&]()->bool{return ThreadMaxNum > 0; }
  );
  --ThreadMaxNum;
   
  while (true);
  std::lock_guard<std::mutex> lock2{ Semaphore_mutex };
  ++ThreadMaxNum;
  condition.notify_one();  
}

但這樣最多只能有 1 個線程在跑那跑不出來的 loop。
必須改寫成這樣才正確
void F2()
{  
  {
    std::unique_lock<std::mutex> lock1{ Semaphore_mutex };
    condition.wait(lock1,
      [&]()->bool{return ThreadMaxNum > 0; }
    );
    --ThreadMaxNum;
  }
 
  while (true);
  {
    std::lock_guard<std::mutex> lock2{ Semaphore_mutex };
    ++ThreadMaxNum;
    condition.notify_one();
  }
}

藉由區塊的結束執行 lock1 及 lock2 的解構函數。
所以 Semaphore 的作用只是像個閘門,可以讓多少線程通過這道
門,而不是讓多少線程可以鎖住使用權


沒有留言:

張貼留言