1. 程式人生 > >執行緒相關的單件模式(Thread-Specific Singelton)

執行緒相關的單件模式(Thread-Specific Singelton)

  單件(Singelton)模式可以說是眾多設計模式當中,理解起來最容易,概念最為簡單的一個。並且在實際的設計當中也是使用得又最為頻繁的,甚至有很多其它的模式都要藉助單件才能更好地實現。然而就是這樣被強烈需求的“一句話模式”(一句話就能闡述明白),雖然有無數的牛人浸淫其中,至今也沒有誰鼓搗出一個完美的實現。我小菜鳥一隻自然更不敢逢人便談單件。不過這個貼的主題是跟單件模式是密不可分的。

  什麼又叫做“執行緒相關的單件模式”呢?也許你已經顧名思義猜出了八九分。不過還是允許我簡單地用例項說明一下。

  假設你要設計了一個簡單的 GUI 框架,這個框架當中需要這樣一個全域性變數(單件模式),它儲存了所有視窗控制代碼與視窗指標的對映(我見過的數個的開源 GUI 框架都有類似的東西。)。在 WIN32 平臺上就是這樣一個簡單的東西:

    //視窗的包裝類
class Window
{
HWND m_hwnd;
public:
bool create();
bool destroy();

//其它細節
};

//視窗控制代碼與其物件指標的對映
typedef map<HWND,Window*> WindowMap;
typedef WindowMap::iterator WindowIter;
WindowMap theWindowMap;




  每建立一個視窗,就需要往這個 theWindowMap 當中新增對映。每銷燬一個視窗,則需要從其中刪除掉相關對映。實現程式碼類似:

    //建立視窗
bool Window::create()
{
m_hwnd=::CreateWindow(/*引數略*/);
if(!::IsWindow(m_hwnd))
return false;

theWindowMap[m_hwnd]=this; //新增對映
return true;
}

//銷燬視窗
bool Window::destroy()
{
::DestroyWindow(m_hwnd);

theWindowMap.erase(m_hwnd); //刪除對映

return true;
}


  你可以用任何可能的單件模式來實現這樣一個全域性變數 theWindowMap,它會 工作得很好。但是當如果考慮要給程式新增多執行緒支援(“多執行緒”是如此麻煩,它總愛和“但是”一起出現,給本來進行得很順利的事情引起波折。),就會發現此時也許純粹的單件模式並不是最好的選擇。例如一個執行緒同時建立視窗,那麼兩個執行緒同時呼叫:

    theWindowMap[m_hwnd]=this;


  這顯然不是一個原子操作,可以肯定如果你堅持這樣幹你的程式會慢慢走向崩潰,幸運一點只是程式執行結果錯誤,如果你恰好那幾天印堂發暗面色發灰,說不定就因為這小小的錯誤,被無良的BOSS作為藉口開除掉了,那可是個悲慘的結局。

  當然大多數的單件模式已經考慮到了多執行緒的問題。它們的解決方案就是給加上執行緒鎖 ,我在數個開源的 GUI 框架看到他們都採用了這種解決方案。不過這樣做,線上程同步過程當中,會產生與 GUI 框架邏輯不相關的同步消耗,雖然不是什麼大不了的消耗,但是客戶可能因此就選擇了你的竟爭對手,如果執行緒竟爭激烈,在強烈渴求資源的環境(如小型移動設定)當中,這種消耗更是不可忽視的。

  實際上在應用當中,極少有執行緒需要插入刪除其它執行緒建立的視窗對映(如果確實有這種需要,那麼可以肯定專案的設計上出了問題)。在這種情況下本執行緒建立視窗對映都將只是本執行緒存取,類似“Thread-Specific”的概念。也就是說,theWindowMap 當中其它執行緒建立的視窗的對映對於本執行緒來說都是不需關心的,我們卻要為那部分不必要東西整天提心吊膽並付出執行時消耗的代價,這也有點像“穿著棉襖洗澡”。但是怎麼樣才能做到更輕鬆爽快些呢?

  就本例問題而言,我們需要這樣一種變數來儲存視窗對映,它針對每個執行緒有不同的值(Thread-Specific Data),這些值互不影響,並且所有執行緒對它的訪問如同是在訪問一個程序內的全域性變數(Singelton)。

  如果你是熟悉多執行緒程式設計的人,那麼“Thread-Specific ”一定讓你想起了什麼。是的,“Thread-Specific Storage ” (執行緒相關存存諸,簡稱 TSS ),正是我們需要的,這是大多數作業系統都提供了的一種執行緒公共資源安全機制,這種機制允許以一定方式建立一個變數,這個變數在所在程序當中的每個執行緒當中,可以擁有不同的值。在 WIN32 上,這個變數就稱為“索引”,其相關的值則稱為“槽”, “Thread-Local Storage”(執行緒區域性存諸,簡稱 TLS )機制。它的提了供這樣幾個函式來定義,設定,讀取執行緒相關資料(關於 TLS 的更多資訊,可以查閱 MSDN ):

    //申請一個“槽”的索引。
DWORD TlsAlloc( void );

//獲得呼叫執行緒當中指定“槽”的值。
VOID* TlsGetValue( DWORD dwTlsIndex );

//設定呼叫執行緒當中指定“槽”的值。
BOOL TlsSetValue( DWORD dwTlsIndex,VOID* lpTlsValue );

//釋放掉申請的“槽”的索引
BOOL TlsFree( DWORD dwTlsIndex );

  具體使用流程方法:先呼叫 TlsAlloc 申請一個“索引”,然後執行緒在適當時機建立一個物件並呼叫 TlsSetValue 將“索引”對應的“槽”設定為該物件的指標,在此之後即可用 TlsGetValue 訪問該“糟”。最後在不需要的時候呼叫 TlsFree ,如在本例當中,呼叫 TlsFree 的最佳時機是在程序結束時。

  先封裝一下 TlsAlloc 和 TlsFree  以方便對 ”索引“的管理。

    class TlsIndex
{
public:
TlsIndex()
:m_index(::TlsAlloc())
{}

~TlsIndex()
{
::TlsFree(m_index);
}

public:
operator DWORD() const
{
return m_index;
}

private:
DWORD m_index;
};

  
  如你所見,類 TlsIndex 將在構造的時候申請一個“索引”,在析構的時候釋放此“索引”。

  在本例當中 TlsIndex 的物件應該存在程序的生命周內,以保證在程序退出之前,這個“索引”都不會被釋放,這樣的 TlsIndex 物件聽起來正像一個全域性靜態物件,不過 Meyers Singelton (用函式內的靜態物件實現)在這裡會更適合,因為我們不需要對這個物件的生命週末進行精確控制,只需要它在需要的時候建立,然後在程序結束前銷燬即可。這種方式只需要很少的程式碼即可實現,比如:

    DWORD windowMapTlsIndex()
{
static TlsIndex s_ti;  //提供自動管理生命週期的“索引”
return s_ti;
}


  利用這個“索引”,我們就能實現上述“Thread-Specific”的功能:

    WindowMap* windowMap()
{
WindowMap* wp=reinterpret_cast<WindowMap*>(::TlsGetValue(windowMapTlsIndex()));
if(!wp)
{
wp=new WindowMap();
::TlsSetValue(windowMapTlsIndex(),wp);
}
return wp;
}

#define theWindowMap *(windowMap())

  
  注意各執行緒訪問以上的程式碼不會存在竟爭。這樣就實現了一個執行緒安全且無執行緒同步消耗版本的“全域性物件” theWindowMap 。我們甚至不用改變Window::create,Window::destory,queryWindow 的程式碼,

  這幾個簡單的函式看起來似乎不像一個“模式”,但是它確實是的。

  現在總結一下“執行緒相關的單件模式”的概念:保證一個類在一個執行緒當中只有一個例項,並提供一個訪問它的執行緒內的訪問點的模式。

  為了不重複地製造車輪,我將此類應用的模式封裝了一下:

    template<typename TDerived>
class TlsSingelton
{
typedef TDerived _Derived;
typedef TlsSingelton<TDerived> _Base;

public:
static _Derived* tlsInstance()
{
return tlsCreate();
}

protected:
static _Derived* tlsCreate()
{
_Derived* derived=tlsGet();
if(derived)
return derived;

derived=new _Derived();
if(derived && TRUE==::TlsSetValue(tlsIndex(),derived))
return derived;

if(derived)
delete derived;

return NULL;
}

static bool tlsDestroy()
{
_Derived* derived=tlsGet();
if(!derived)
return false;

delete derived;
return true;
}

static DWORD tlsIndex()
{
static TlsIndex s_tlsIndex;
return s_tlsIndex;
}

private:
static _Derived* tlsGet()
{
return reinterpret_cast<_Derived*>(::TlsGetValue(tlsIndex()));
}

static bool tlsSet(_Derived* derived)
{
return TRUE==::TlsSetValue(tlsIndex(),derived);
}

//noncopyable
private:
TlsSingelton(const _Base&);
TlsSingelton& operator=(const _Base&);
};


  將 tlsCreate,tlsDestroy 兩個函式設定為保護成員,是為了防止一些不三不四吊爾啷噹的程式隨意地刪除。

  示例:

    class WindowMapImpl:public TlsSingelton<WindowMap>
{
WindowMap m_map;
public:
WidnowMap& theWindowMapImpl()
{
return m_map;
}

public:
~WindowMapImpl();

protected:
WindowMapImpl(); //只能通過tlsCreate建立
friend class _Base;
};

#define theWindowMap (WindowMapImpl::tlsInstance()->theWindowMapImpl())



  仍不需要修改原有視窗程式碼。